Browse Source

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.
main
Shady Nagy 4 years ago
committed by GitHub
parent
commit
9db2feb930
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 134
      .editorconfig
  2. 3
      .github/workflows/dotnetcore.yml
  3. 6
      global.json
  4. 2
      src/ApplicationCore/ApplicationCore.csproj
  5. 9
      src/ApplicationCore/CatalogSettings.cs
  6. 17
      src/ApplicationCore/Constants/AuthorizationConstants.cs
  7. 13
      src/ApplicationCore/Entities/BaseEntity.cs
  8. 55
      src/ApplicationCore/Entities/BasketAggregate/Basket.cs
  9. 45
      src/ApplicationCore/Entities/BasketAggregate/BasketItem.cs
  10. 33
      src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs
  11. 13
      src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs
  12. 13
      src/ApplicationCore/Entities/CatalogBrand.cs
  13. 107
      src/ApplicationCore/Entities/CatalogItem.cs
  14. 13
      src/ApplicationCore/Entities/CatalogType.cs
  15. 33
      src/ApplicationCore/Entities/OrderAggregate/Address.cs
  16. 45
      src/ApplicationCore/Entities/OrderAggregate/CatalogItemOrdered.cs
  17. 75
      src/ApplicationCore/Entities/OrderAggregate/Order.cs
  18. 31
      src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs
  19. 9
      src/ApplicationCore/Exceptions/BasketNotFoundException.cs
  20. 14
      src/ApplicationCore/Exceptions/DuplicateException.cs
  21. 29
      src/ApplicationCore/Exceptions/EmptyBasketOnCheckoutException.cs
  22. 31
      src/ApplicationCore/Extensions/GuardExtensions.cs
  23. 21
      src/ApplicationCore/Extensions/JsonExtensions.cs
  24. 9
      src/ApplicationCore/Interfaces/IAggregateRoot.cs
  25. 19
      src/ApplicationCore/Interfaces/IAppLogger.cs
  26. 9
      src/ApplicationCore/Interfaces/IBasketQueryService.cs
  27. 19
      src/ApplicationCore/Interfaces/IBasketService.cs
  28. 10
      src/ApplicationCore/Interfaces/IEmailSender.cs
  29. 13
      src/ApplicationCore/Interfaces/IOrderService.cs
  30. 7
      src/ApplicationCore/Interfaces/IReadRepository.cs
  31. 7
      src/ApplicationCore/Interfaces/IRepository.cs
  32. 9
      src/ApplicationCore/Interfaces/ITokenClaimsService.cs
  33. 9
      src/ApplicationCore/Interfaces/IUriComposer.cs
  34. 127
      src/ApplicationCore/Services/BasketService.cs
  35. 75
      src/ApplicationCore/Services/OrderService.cs
  36. 17
      src/ApplicationCore/Services/UriComposer.cs
  37. 27
      src/ApplicationCore/Specifications/BasketWithItemsSpecification.cs
  38. 23
      src/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs
  39. 13
      src/ApplicationCore/Specifications/CatalogFilterSpecification.cs
  40. 11
      src/ApplicationCore/Specifications/CatalogItemNameSpecification.cs
  41. 17
      src/ApplicationCore/Specifications/CatalogItemsSpecification.cs
  42. 15
      src/ApplicationCore/Specifications/CustomerOrdersWithItemsSpecification.cs
  43. 17
      src/ApplicationCore/Specifications/OrderWithItemsByIdSpec.cs
  44. 2
      src/BlazorAdmin/BlazorAdmin.csproj
  45. 119
      src/BlazorAdmin/CustomAuthStateProvider.cs
  46. 35
      src/BlazorAdmin/Helpers/BlazorComponent.cs
  47. 33
      src/BlazorAdmin/Helpers/BlazorLayoutComponent.cs
  48. 31
      src/BlazorAdmin/Helpers/RefreshBroadcast.cs
  49. 159
      src/BlazorAdmin/Helpers/ToastComponent.cs
  50. 35
      src/BlazorAdmin/JavaScript/Cookies.cs
  51. 35
      src/BlazorAdmin/JavaScript/Css.cs
  52. 17
      src/BlazorAdmin/JavaScript/JSInteropConstants.cs
  53. 27
      src/BlazorAdmin/JavaScript/Route.cs
  54. 99
      src/BlazorAdmin/Pages/CatalogItemPage/List.razor.cs
  55. 61
      src/BlazorAdmin/Program.cs
  56. 23
      src/BlazorAdmin/Services/CacheEntry.cs
  57. 173
      src/BlazorAdmin/Services/CachedCatalogItemServiceDecorator.cs
  58. 79
      src/BlazorAdmin/Services/CachedCatalogLookupDataServiceDecorator .cs
  59. 155
      src/BlazorAdmin/Services/CatalogItemService.cs
  60. 59
      src/BlazorAdmin/Services/CatalogLookupDataService.cs
  61. 135
      src/BlazorAdmin/Services/HttpService.cs
  62. 79
      src/BlazorAdmin/Services/ToastService.cs
  63. 23
      src/BlazorAdmin/ServicesConfiguration.cs
  64. 49
      src/BlazorAdmin/Shared/CustomInputSelect.cs
  65. 9
      src/BlazorShared/Attributes/EndpointAttribute.cs
  66. 25
      src/BlazorShared/Authorization/ClaimValue.cs
  67. 11
      src/BlazorShared/Authorization/Constants.cs
  68. 19
      src/BlazorShared/Authorization/UserInfo.cs
  69. 13
      src/BlazorShared/BaseUrlConfiguration.cs
  70. 2
      src/BlazorShared/BlazorShared.csproj
  71. 19
      src/BlazorShared/Interfaces/ICatalogItemService.cs
  72. 13
      src/BlazorShared/Interfaces/ICatalogLookupDataService.cs
  73. 13
      src/BlazorShared/Interfaces/ILookupDataResponse.cs
  74. 9
      src/BlazorShared/Models/CatalogBrand.cs
  75. 15
      src/BlazorShared/Models/CatalogBrandResponse.cs
  76. 117
      src/BlazorShared/Models/CatalogItem.cs
  77. 9
      src/BlazorShared/Models/CatalogType.cs
  78. 15
      src/BlazorShared/Models/CatalogTypeResponse.cs
  79. 35
      src/BlazorShared/Models/CreateCatalogItemRequest.cs
  80. 9
      src/BlazorShared/Models/CreateCatalogItemResponse.cs
  81. 9
      src/BlazorShared/Models/DeleteCatalogItemResponse.cs
  82. 9
      src/BlazorShared/Models/EditCatalogItemResponse.cs
  83. 15
      src/BlazorShared/Models/ErrorDetails.cs
  84. 11
      src/BlazorShared/Models/LookupData.cs
  85. 11
      src/BlazorShared/Models/PagedCatalogItemResponse.cs
  86. 37
      src/Infrastructure/Data/BasketQueryService.cs
  87. 38
      src/Infrastructure/Data/CatalogContext.cs
  88. 105
      src/Infrastructure/Data/CatalogContextSeed.cs
  89. 19
      src/Infrastructure/Data/Config/BasketConfiguration.cs
  90. 15
      src/Infrastructure/Data/Config/BasketItemConfiguration.cs
  91. 23
      src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs
  92. 45
      src/Infrastructure/Data/Config/CatalogItemConfiguration.cs
  93. 23
      src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs
  94. 69
      src/Infrastructure/Data/Config/OrderConfiguration.cs
  95. 27
      src/Infrastructure/Data/Config/OrderItemConfiguration.cs
  96. 11
      src/Infrastructure/Data/EfRepository.cs
  97. 21
      src/Infrastructure/Data/FileItem.cs
  98. 399
      src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs
  99. 87
      src/Infrastructure/Data/Migrations/20211026175614_FixBuyerId.cs
  100. 26
      src/Infrastructure/Identity/AppIdentityDbContext.cs

134
.editorconfig

@ -0,0 +1,134 @@
###############################
# Core EditorConfig Options #
###############################
root = true
# All files
[*]
indent_style = space
# XML project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# XML config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom
###############################
# .NET Coding Conventions #
###############################
[*.{cs,vb}]
# Organize usings
dotnet_sort_system_directives_first = true
# this. preferences
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
###############################
# Naming Conventions #
###############################
# Style Definitions
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Use PascalCase for constant fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
dotnet_naming_symbols.constant_fields.required_modifiers = const
###############################
# C# Coding Conventions #
###############################
[*.cs]
# var preferences
csharp_style_var_for_built_in_types = true:silent
csharp_style_var_when_type_is_apparent = true:silent
csharp_style_var_elsewhere = true:silent
# Expression-bodied members
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
# Null-checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
# Expression-level preferences
csharp_prefer_braces = true:silent
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
# Namespaces
csharp_style_namespace_declarations = file_scoped:warning
###############################
# C# Formatting Rules #
###############################
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
###############################
# VB Coding Conventions #
###############################
[*.vb]
# Modifier preferences
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion

3
.github/workflows/dotnetcore.yml

@ -12,7 +12,8 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: '5.0.x' dotnet-version: '6.0.x'
include-prerelease: true
- name: Build with dotnet - name: Build with dotnet
run: dotnet build ./eShopOnWeb.sln --configuration Release run: dotnet build ./eShopOnWeb.sln --configuration Release

6
global.json

@ -0,0 +1,6 @@
{
"sdk": {
"version": "6.0.x",
"rollForward": "latestFeature"
}
}

2
src/ApplicationCore/ApplicationCore.csproj

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

9
src/ApplicationCore/CatalogSettings.cs

@ -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; }
}
} }

17
src/ApplicationCore/Constants/AuthorizationConstants.cs

@ -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 // TODO: Don't use this in production
public const string DEFAULT_PASSWORD = "Pass@word1"; public const string DEFAULT_PASSWORD = "Pass@word1";
// TODO: Change this to an environment variable // TODO: Change this to an environment variable
public const string JWT_SECRET_KEY = "SecretKeyOfDoomThatMustBeAMinimumNumberOfBytes"; public const string JWT_SECRET_KEY = "SecretKeyOfDoomThatMustBeAMinimumNumberOfBytes";
}
} }

13
src/ApplicationCore/Entities/BaseEntity.cs

@ -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. public virtual int Id { get; protected set; }
// Using non-generic integer types for simplicity and to ease caching logic
public abstract class BaseEntity
{
public virtual int Id { get; protected set; }
}
} }

55
src/ApplicationCore/Entities/BasketAggregate/Basket.cs

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

45
src/ApplicationCore/Entities/BasketAggregate/BasketItem.cs

@ -1,34 +1,33 @@
using Ardalis.GuardClauses; 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 decimal UnitPrice { get; private set; }
public int Quantity { get; private set; } public int Quantity { get; private set; }
public int CatalogItemId { get; private set; } public int CatalogItemId { get; private set; }
public int BasketId { get; private set; } public int BasketId { get; private set; }
public BasketItem(int catalogItemId, int quantity, decimal unitPrice) public BasketItem(int catalogItemId, int quantity, decimal unitPrice)
{ {
CatalogItemId = catalogItemId; CatalogItemId = catalogItemId;
UnitPrice = unitPrice; UnitPrice = unitPrice;
SetQuantity(quantity); SetQuantity(quantity);
} }
public void AddQuantity(int quantity) public void AddQuantity(int quantity)
{ {
Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue); Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue);
Quantity += quantity; Quantity += quantity;
} }
public void SetQuantity(int quantity) public void SetQuantity(int quantity)
{ {
Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue); Guard.Against.OutOfRange(quantity, nameof(quantity), 0, int.MaxValue);
Quantity = quantity; Quantity = quantity;
}
} }
} }

33
src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs

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

13
src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs

@ -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 Alias { get; private set; } public string Last4 { 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; }
}
} }

13
src/ApplicationCore/Entities/CatalogBrand.cs

@ -1,13 +1,12 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces; 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; } Brand = brand;
public CatalogBrand(string brand)
{
Brand = brand;
}
} }
} }

107
src/ApplicationCore/Entities/CatalogItem.cs

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

13
src/ApplicationCore/Entities/CatalogType.cs

@ -1,13 +1,12 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces; 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; } Type = type;
public CatalogType(string type)
{
Type = type;
}
} }
} }

33
src/ApplicationCore/Entities/OrderAggregate/Address.cs

@ -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 Street { get; private set; }
public string City { get; private set; } public string City { get; private set; }
public string State { get; private set; } public string State { get; private set; }
public string Country { get; private set; } public string Country { get; private set; }
public string ZipCode { get; private set; } public string ZipCode { get; private set; }
private Address() { } private Address() { }
public Address(string street, string city, string state, string country, string zipcode) public Address(string street, string city, string state, string country, string zipcode)
{ {
Street = street; Street = street;
City = city; City = city;
State = state; State = state;
Country = country; Country = country;
ZipCode = zipcode; ZipCode = zipcode;
}
} }
} }

45
src/ApplicationCore/Entities/OrderAggregate/CatalogItemOrdered.cs

@ -1,31 +1,30 @@
using Ardalis.GuardClauses; 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> public CatalogItemOrdered(int catalogItemId, string productName, string pictureUri)
/// 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) Guard.Against.OutOfRange(catalogItemId, nameof(catalogItemId), 1, int.MaxValue);
{ Guard.Against.NullOrEmpty(productName, nameof(productName));
Guard.Against.OutOfRange(catalogItemId, nameof(catalogItemId), 1, int.MaxValue); Guard.Against.NullOrEmpty(pictureUri, nameof(pictureUri));
Guard.Against.NullOrEmpty(productName, nameof(productName));
Guard.Against.NullOrEmpty(pictureUri, nameof(pictureUri));
CatalogItemId = catalogItemId;
ProductName = productName;
PictureUri = pictureUri;
}
private CatalogItemOrdered() CatalogItemId = catalogItemId;
{ ProductName = productName;
// required by EF PictureUri = pictureUri;
} }
public int CatalogItemId { get; private set; } private CatalogItemOrdered()
public string ProductName { get; private set; } {
public string PictureUri { get; private set; } // required by EF
} }
public int CatalogItemId { get; private set; }
public string ProductName { get; private set; }
public string PictureUri { get; private set; }
} }

75
src/ApplicationCore/Entities/OrderAggregate/Order.cs

@ -1,52 +1,51 @@
using Ardalis.GuardClauses; using System;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using System;
using System.Collections.Generic; 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
{ }
// required by EF
}
public Order(string buyerId, Address shipToAddress, List<OrderItem> items) public Order(string buyerId, Address shipToAddress, List<OrderItem> items)
{ {
Guard.Against.NullOrEmpty(buyerId, nameof(buyerId)); Guard.Against.NullOrEmpty(buyerId, nameof(buyerId));
Guard.Against.Null(shipToAddress, nameof(shipToAddress)); Guard.Against.Null(shipToAddress, nameof(shipToAddress));
Guard.Against.Null(items, nameof(items)); Guard.Against.Null(items, nameof(items));
BuyerId = buyerId; BuyerId = buyerId;
ShipToAddress = shipToAddress; ShipToAddress = shipToAddress;
_orderItems = items; _orderItems = items;
} }
public string BuyerId { get; private set; } public string BuyerId { get; private set; }
public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now; public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now;
public Address ShipToAddress { get; private set; } public Address ShipToAddress { get; private set; }
// DDD Patterns comment // DDD Patterns comment
// Using a private collection field, better for DDD Aggregate's encapsulation // Using a private collection field, better for DDD Aggregate's encapsulation
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection, // so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
// but only through the method Order.AddOrderItem() which includes behavior. // but only through the method Order.AddOrderItem() which includes behavior.
private readonly List<OrderItem> _orderItems = new List<OrderItem>(); private readonly List<OrderItem> _orderItems = new List<OrderItem>();
// Using List<>.AsReadOnly() // Using List<>.AsReadOnly()
// This will create a read only wrapper around the private list so is protected against "external updates". // 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) // 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 //https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly(); public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();
public decimal Total() public decimal Total()
{
var total = 0m;
foreach (var item in _orderItems)
{ {
var total = 0m; total += item.UnitPrice * item.Units;
foreach (var item in _orderItems)
{
total += item.UnitPrice * item.Units;
}
return total;
} }
return total;
} }
} }

31
src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs

@ -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 CatalogItemOrdered ItemOrdered { get; private set; } public int Units { get; private set; }
public decimal UnitPrice { get; private set; }
public int Units { get; private set; }
private OrderItem() private OrderItem()
{ {
// required by EF // required by EF
} }
public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units) public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units)
{ {
ItemOrdered = itemOrdered; ItemOrdered = itemOrdered;
UnitPrice = unitPrice; UnitPrice = unitPrice;
Units = units; Units = units;
}
} }
} }

9
src/ApplicationCore/Exceptions/BasketNotFoundException.cs

@ -1,11 +1,10 @@
using System; 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}")
{
}
} }
} }

14
src/ApplicationCore/Exceptions/DuplicateException.cs

@ -1,14 +1,12 @@
using System; 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)
{
}
} }
} }

29
src/ApplicationCore/Exceptions/EmptyBasketOnCheckoutException.cs

@ -1,24 +1,23 @@
using System; 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)
{ {
}
} }
} }

31
src/ApplicationCore/Extensions/GuardExtensions.cs

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

21
src/ApplicationCore/Extensions/JsonExtensions.cs

@ -1,18 +1,17 @@
using System.Text.Json; 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) => public static T FromJson<T>(this string json) =>
JsonSerializer.Deserialize<T>(json, _jsonOptions); JsonSerializer.Deserialize<T>(json, _jsonOptions);
public static string ToJson<T>(this T obj) => public static string ToJson<T>(this T obj) =>
JsonSerializer.Serialize<T>(obj, _jsonOptions); JsonSerializer.Serialize<T>(obj, _jsonOptions);
}
} }

9
src/ApplicationCore/Interfaces/IAggregateRoot.cs

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

19
src/ApplicationCore/Interfaces/IAppLogger.cs

@ -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> void LogInformation(string message, params object[] args);
/// This type eliminates the need to depend directly on the ASP.NET Core logging types. void LogWarning(string message, params object[] args);
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IAppLogger<T>
{
void LogInformation(string message, params object[] args);
void LogWarning(string message, params object[] args);
}
} }

9
src/ApplicationCore/Interfaces/IBasketQueryService.cs

@ -1,9 +1,8 @@
using System.Threading.Tasks; 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);
}
} }

19
src/ApplicationCore/Interfaces/IBasketService.cs

@ -1,14 +1,13 @@
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks; 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 TransferBasketAsync(string anonymousId, string userName); Task<Basket> SetQuantities(int basketId, Dictionary<string, int> quantities);
Task<Basket> AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1); Task DeleteBasketAsync(int basketId);
Task<Basket> SetQuantities(int basketId, Dictionary<string, int> quantities);
Task DeleteBasketAsync(int basketId);
}
} }

10
src/ApplicationCore/Interfaces/IEmailSender.cs

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

13
src/ApplicationCore/Interfaces/IOrderService.cs

@ -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);
}
} }

7
src/ApplicationCore/Interfaces/IReadRepository.cs

@ -1,8 +1,7 @@
using Ardalis.Specification; 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
{
}
} }

7
src/ApplicationCore/Interfaces/IRepository.cs

@ -1,8 +1,7 @@
using Ardalis.Specification; 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
{
}
} }

9
src/ApplicationCore/Interfaces/ITokenClaimsService.cs

@ -1,9 +1,8 @@
using System.Threading.Tasks; 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);
}
} }

9
src/ApplicationCore/Interfaces/IUriComposer.cs

@ -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);
}
} }

127
src/ApplicationCore/Services/BasketService.cs

@ -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.Entities.BasketAggregate;
using Microsoft.eShopWeb.ApplicationCore.Interfaces; using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.ApplicationCore.Specifications; 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; _basketRepository = basketRepository;
private readonly IAppLogger<BasketService> _logger; _logger = logger;
}
public BasketService(IRepository<Basket> basketRepository, public async Task<Basket> AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1)
IAppLogger<BasketService> logger) {
{ var basketSpec = new BasketWithItemsSpecification(username);
_basketRepository = basketRepository; var basket = await _basketRepository.GetBySpecAsync(basketSpec);
_logger = logger;
}
public async Task<Basket> AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1) if (basket == null)
{ {
var basketSpec = new BasketWithItemsSpecification(username); basket = new Basket(username);
var basket = await _basketRepository.GetBySpecAsync(basketSpec); await _basketRepository.AddAsync(basket);
}
if (basket == null) basket.AddItem(catalogItemId, price, quantity);
{
basket = new Basket(username);
await _basketRepository.AddAsync(basket);
}
basket.AddItem(catalogItemId, price, quantity); await _basketRepository.UpdateAsync(basket);
return basket;
}
await _basketRepository.UpdateAsync(basket); public async Task DeleteBasketAsync(int basketId)
return basket; {
} var basket = await _basketRepository.GetByIdAsync(basketId);
await _basketRepository.DeleteAsync(basket);
}
public async Task DeleteBasketAsync(int basketId) public async Task<Basket> SetQuantities(int basketId, Dictionary<string, int> quantities)
{ {
var basket = await _basketRepository.GetByIdAsync(basketId); Guard.Against.Null(quantities, nameof(quantities));
await _basketRepository.DeleteAsync(basket); var basketSpec = new BasketWithItemsSpecification(basketId);
} var basket = await _basketRepository.GetBySpecAsync(basketSpec);
Guard.Against.NullBasket(basketId, basket);
public async Task<Basket> SetQuantities(int basketId, Dictionary<string, int> quantities) foreach (var item in basket.Items)
{ {
Guard.Against.Null(quantities, nameof(quantities)); if (quantities.TryGetValue(item.Id.ToString(), out var quantity))
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);
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;
} }
basket.RemoveEmptyItems();
await _basketRepository.UpdateAsync(basket);
return basket;
}
public async Task TransferBasketAsync(string anonymousId, string userName) 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)
{ {
Guard.Against.NullOrEmpty(anonymousId, nameof(anonymousId)); userBasket = new Basket(userName);
Guard.Against.NullOrEmpty(userName, nameof(userName)); await _basketRepository.AddAsync(userBasket);
var anonymousBasketSpec = new BasketWithItemsSpecification(anonymousId); }
var anonymousBasket = await _basketRepository.GetBySpecAsync(anonymousBasketSpec); foreach (var item in anonymousBasket.Items)
if (anonymousBasket == null) return; {
var userBasketSpec = new BasketWithItemsSpecification(userName); userBasket.AddItem(item.CatalogItemId, item.UnitPrice, item.Quantity);
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);
} }
await _basketRepository.UpdateAsync(userBasket);
await _basketRepository.DeleteAsync(anonymousBasket);
} }
} }

75
src/ApplicationCore/Services/OrderService.cs

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

17
src/ApplicationCore/Services/UriComposer.cs

@ -1,16 +1,15 @@
using Microsoft.eShopWeb.ApplicationCore.Interfaces; 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;
{
private readonly CatalogSettings _catalogSettings;
public UriComposer(CatalogSettings catalogSettings) => _catalogSettings = catalogSettings; public UriComposer(CatalogSettings catalogSettings) => _catalogSettings = catalogSettings;
public string ComposePicUri(string uriTemplate) public string ComposePicUri(string uriTemplate)
{ {
return uriTemplate.Replace("http://catalogbaseurltobereplaced", _catalogSettings.CatalogBaseUrl); return uriTemplate.Replace("http://catalogbaseurltobereplaced", _catalogSettings.CatalogBaseUrl);
}
} }
} }

27
src/ApplicationCore/Specifications/BasketWithItemsSpecification.cs

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

23
src/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs

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

13
src/ApplicationCore/Specifications/CatalogFilterSpecification.cs

@ -1,14 +1,13 @@
using Ardalis.Specification; using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities; 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));
}
} }
} }

11
src/ApplicationCore/Specifications/CatalogItemNameSpecification.cs

@ -1,13 +1,12 @@
using Ardalis.Specification; using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities; 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);
}
} }
} }

17
src/ApplicationCore/Specifications/CatalogItemsSpecification.cs

@ -1,15 +1,14 @@
using Ardalis.Specification; using System;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using System;
using System.Linq; 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));
}
} }
} }

15
src/ApplicationCore/Specifications/CustomerOrdersWithItemsSpecification.cs

@ -1,15 +1,14 @@
using Ardalis.Specification; using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 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)
Query.Where(o => o.BuyerId == buyerId) .ThenInclude(i => i.ItemOrdered);
.Include(o => o.OrderItems)
.ThenInclude(i => i.ItemOrdered);
}
} }
} }

17
src/ApplicationCore/Specifications/OrderWithItemsByIdSpec.cs

@ -1,16 +1,15 @@
using Ardalis.Specification; using Ardalis.Specification;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; 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)
Query .Include(o => o.OrderItems)
.Where(order => order.Id == orderId) .ThenInclude(i => i.ItemOrdered);
.Include(o => o.OrderItems)
.ThenInclude(i => i.ItemOrdered);
}
} }
} }

2
src/BlazorAdmin/BlazorAdmin.csproj

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

119
src/BlazorAdmin/CustomAuthStateProvider.cs

@ -1,87 +1,86 @@
using BlazorShared.Authorization; using System;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;
using System;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlazorShared.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;
namespace BlazorAdmin;
namespace BlazorAdmin public class CustomAuthStateProvider : AuthenticationStateProvider
{ {
public class CustomAuthStateProvider : AuthenticationStateProvider // TODO: Get Default Cache Duration from Config
{ private static readonly TimeSpan UserCacheRefreshInterval = TimeSpan.FromSeconds(60);
// TODO: Get Default Cache Duration from Config
private static readonly TimeSpan UserCacheRefreshInterval = TimeSpan.FromSeconds(60);
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly ILogger<CustomAuthStateProvider> _logger; private readonly ILogger<CustomAuthStateProvider> _logger;
private DateTimeOffset _userLastCheck = DateTimeOffset.FromUnixTimeSeconds(0); private DateTimeOffset _userLastCheck = DateTimeOffset.FromUnixTimeSeconds(0);
private ClaimsPrincipal _cachedUser = new ClaimsPrincipal(new ClaimsIdentity()); private ClaimsPrincipal _cachedUser = new ClaimsPrincipal(new ClaimsIdentity());
public CustomAuthStateProvider(HttpClient httpClient, public CustomAuthStateProvider(HttpClient httpClient,
ILogger<CustomAuthStateProvider> logger) ILogger<CustomAuthStateProvider> logger)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_logger = logger; _logger = logger;
} }
public override async Task<AuthenticationState> GetAuthenticationStateAsync() public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
return new AuthenticationState(await GetUser(useCache: true));
}
private async ValueTask<ClaimsPrincipal> GetUser(bool useCache = false)
{
var now = DateTimeOffset.Now;
if (useCache && now < _userLastCheck + UserCacheRefreshInterval)
{ {
return new AuthenticationState(await GetUser(useCache: true)); return _cachedUser;
} }
private async ValueTask<ClaimsPrincipal> GetUser(bool useCache = false) _cachedUser = await FetchUser();
{ _userLastCheck = now;
var now = DateTimeOffset.Now;
if (useCache && now < _userLastCheck + UserCacheRefreshInterval)
{
return _cachedUser;
}
_cachedUser = await FetchUser(); return _cachedUser;
_userLastCheck = now; }
return _cachedUser; private async Task<ClaimsPrincipal> FetchUser()
} {
UserInfo user = null;
private async Task<ClaimsPrincipal> FetchUser() try
{ {
UserInfo user = null; _logger.LogInformation("Fetching user details from web api.");
user = await _httpClient.GetFromJsonAsync<UserInfo>("User");
try }
{ catch (Exception exc)
_logger.LogInformation("Fetching user details from web api."); {
user = await _httpClient.GetFromJsonAsync<UserInfo>("User"); _logger.LogWarning(exc, "Fetching user failed.");
} }
catch (Exception exc)
{
_logger.LogWarning(exc, "Fetching user failed.");
}
if (user == null || !user.IsAuthenticated) if (user == null || !user.IsAuthenticated)
{ {
return null; return null;
} }
var identity = new ClaimsIdentity( var identity = new ClaimsIdentity(
nameof(CustomAuthStateProvider), nameof(CustomAuthStateProvider),
user.NameClaimType, user.NameClaimType,
user.RoleClaimType); user.RoleClaimType);
if (user.Claims != null) if (user.Claims != null)
{
foreach (var claim in user.Claims)
{ {
foreach (var claim in user.Claims) identity.AddClaim(new Claim(claim.Type, claim.Value));
{
identity.AddClaim(new Claim(claim.Type, claim.Value));
}
} }
}
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", user.Token); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", user.Token);
return new ClaimsPrincipal(identity); return new ClaimsPrincipal(identity);
}
} }
} }

35
src/BlazorAdmin/Helpers/BlazorComponent.cs

@ -1,26 +1,25 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace BlazorAdmin.Helpers namespace BlazorAdmin.Helpers;
{
public class BlazorComponent : ComponentBase
{
private readonly RefreshBroadcast _refresh = RefreshBroadcast.Instance;
protected override void OnInitialized() public class BlazorComponent : ComponentBase
{ {
_refresh.RefreshRequested += DoRefresh; private readonly RefreshBroadcast _refresh = RefreshBroadcast.Instance;
base.OnInitialized();
}
public void CallRequestRefresh() protected override void OnInitialized()
{ {
_refresh.CallRequestRefresh(); _refresh.RefreshRequested += DoRefresh;
} base.OnInitialized();
}
private void DoRefresh() public void CallRequestRefresh()
{ {
StateHasChanged(); _refresh.CallRequestRefresh();
} }
private void DoRefresh()
{
StateHasChanged();
} }
} }

33
src/BlazorAdmin/Helpers/BlazorLayoutComponent.cs

@ -1,25 +1,24 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace BlazorAdmin.Helpers namespace BlazorAdmin.Helpers;
public class BlazorLayoutComponent : LayoutComponentBase
{ {
public class BlazorLayoutComponent : LayoutComponentBase private readonly RefreshBroadcast _refresh = RefreshBroadcast.Instance;
{
private readonly RefreshBroadcast _refresh = RefreshBroadcast.Instance;
protected override void OnInitialized() protected override void OnInitialized()
{ {
_refresh.RefreshRequested += DoRefresh; _refresh.RefreshRequested += DoRefresh;
base.OnInitialized(); base.OnInitialized();
} }
public void CallRequestRefresh() public void CallRequestRefresh()
{ {
_refresh.CallRequestRefresh(); _refresh.CallRequestRefresh();
} }
private void DoRefresh() private void DoRefresh()
{ {
StateHasChanged(); StateHasChanged();
}
} }
} }

31
src/BlazorAdmin/Helpers/RefreshBroadcast.cs

@ -1,24 +1,23 @@
using System; using System;
namespace BlazorAdmin.Helpers namespace BlazorAdmin.Helpers;
internal sealed class RefreshBroadcast
{ {
internal sealed class RefreshBroadcast private static readonly Lazy<RefreshBroadcast>
{ Lazy =
private static readonly Lazy<RefreshBroadcast> new Lazy<RefreshBroadcast>
Lazy = (() => new RefreshBroadcast());
new Lazy<RefreshBroadcast>
(() => new RefreshBroadcast());
public static RefreshBroadcast Instance => Lazy.Value; public static RefreshBroadcast Instance => Lazy.Value;
private RefreshBroadcast() private RefreshBroadcast()
{ {
} }
public event Action RefreshRequested; public event Action RefreshRequested;
public void CallRequestRefresh() public void CallRequestRefresh()
{ {
RefreshRequested?.Invoke(); RefreshRequested?.Invoke();
}
} }
} }

159
src/BlazorAdmin/Helpers/ToastComponent.cs

@ -1,88 +1,87 @@
using BlazorAdmin.Services; using System;
using BlazorAdmin.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using System;
namespace BlazorAdmin.Helpers namespace BlazorAdmin.Helpers;
public class ToastComponent : ComponentBase, IDisposable
{ {
public class ToastComponent : ComponentBase, IDisposable [Inject]
ToastService ToastService
{ {
[Inject] get;
ToastService ToastService set;
{ }
get; protected string Heading
set; {
} get;
protected string Heading set;
{ }
get; protected string Message
set; {
} get;
protected string Message set;
{ }
get; protected bool IsVisible
set; {
} get;
protected bool IsVisible set;
{ }
get; protected string BackgroundCssClass
set; {
} get;
protected string BackgroundCssClass set;
{ }
get; protected string IconCssClass
set; {
} get;
protected string IconCssClass set;
{ }
get; protected override void OnInitialized()
set; {
} ToastService.OnShow += ShowToast;
protected override void OnInitialized() ToastService.OnHide += HideToast;
{ }
ToastService.OnShow += ShowToast; private void ShowToast(string message, ToastLevel level)
ToastService.OnHide += HideToast; {
} BuildToastSettings(level, message);
private void ShowToast(string message, ToastLevel level) IsVisible = true;
{ StateHasChanged();
BuildToastSettings(level, message); }
IsVisible = true; private void HideToast()
StateHasChanged(); {
} IsVisible = false;
private void HideToast() StateHasChanged();
{ }
IsVisible = false; private void BuildToastSettings(ToastLevel level, string message)
StateHasChanged(); {
} switch (level)
private void BuildToastSettings(ToastLevel level, string message)
{
switch (level)
{
case ToastLevel.Info:
BackgroundCssClass = "bg-info";
IconCssClass = "info";
Heading = "Info";
break;
case ToastLevel.Success:
BackgroundCssClass = "bg-success";
IconCssClass = "check";
Heading = "Success";
break;
case ToastLevel.Warning:
BackgroundCssClass = "bg-warning";
IconCssClass = "exclamation";
Heading = "Warning";
break;
case ToastLevel.Error:
BackgroundCssClass = "bg-danger";
IconCssClass = "times";
Heading = "Error";
break;
}
Message = message;
}
public void Dispose()
{ {
ToastService.OnShow -= ShowToast; case ToastLevel.Info:
BackgroundCssClass = "bg-info";
IconCssClass = "info";
Heading = "Info";
break;
case ToastLevel.Success:
BackgroundCssClass = "bg-success";
IconCssClass = "check";
Heading = "Success";
break;
case ToastLevel.Warning:
BackgroundCssClass = "bg-warning";
IconCssClass = "exclamation";
Heading = "Warning";
break;
case ToastLevel.Error:
BackgroundCssClass = "bg-danger";
IconCssClass = "times";
Heading = "Error";
break;
} }
Message = message;
}
public void Dispose()
{
ToastService.OnShow -= ShowToast;
} }
} }

35
src/BlazorAdmin/JavaScript/Cookies.cs

@ -1,25 +1,24 @@
using Microsoft.JSInterop; using System.Threading.Tasks;
using System.Threading.Tasks; using Microsoft.JSInterop;
namespace BlazorAdmin.JavaScript namespace BlazorAdmin.JavaScript;
public class Cookies
{ {
public class Cookies private readonly IJSRuntime _jsRuntime;
{
private readonly IJSRuntime _jsRuntime;
public Cookies(IJSRuntime jsRuntime) public Cookies(IJSRuntime jsRuntime)
{ {
_jsRuntime = jsRuntime; _jsRuntime = jsRuntime;
} }
public async Task DeleteCookie(string name) public async Task DeleteCookie(string name)
{ {
await _jsRuntime.InvokeAsync<string>(JSInteropConstants.DeleteCookie, name); await _jsRuntime.InvokeAsync<string>(JSInteropConstants.DeleteCookie, name);
} }
public async Task<string> GetCookie(string name) public async Task<string> GetCookie(string name)
{ {
return await _jsRuntime.InvokeAsync<string>(JSInteropConstants.GetCookie, name); return await _jsRuntime.InvokeAsync<string>(JSInteropConstants.GetCookie, name);
}
} }
} }

35
src/BlazorAdmin/JavaScript/Css.cs

@ -1,25 +1,24 @@
using Microsoft.JSInterop; using System.Threading.Tasks;
using System.Threading.Tasks; using Microsoft.JSInterop;
namespace BlazorAdmin.JavaScript namespace BlazorAdmin.JavaScript;
public class Css
{ {
public class Css private readonly IJSRuntime _jsRuntime;
public Css(IJSRuntime jsRuntime)
{ {
private readonly IJSRuntime _jsRuntime; _jsRuntime = jsRuntime;
}
public Css(IJSRuntime jsRuntime) public async Task ShowBodyOverflow()
{ {
_jsRuntime = jsRuntime; await _jsRuntime.InvokeAsync<string>(JSInteropConstants.ShowBodyOverflow);
} }
public async Task ShowBodyOverflow()
{
await _jsRuntime.InvokeAsync<string>(JSInteropConstants.ShowBodyOverflow);
}
public async Task<string> HideBodyOverflow() public async Task<string> HideBodyOverflow()
{ {
return await _jsRuntime.InvokeAsync<string>(JSInteropConstants.HideBodyOverflow); return await _jsRuntime.InvokeAsync<string>(JSInteropConstants.HideBodyOverflow);
}
} }
} }

17
src/BlazorAdmin/JavaScript/JSInteropConstants.cs

@ -1,11 +1,10 @@
namespace BlazorAdmin.JavaScript namespace BlazorAdmin.JavaScript;
public static class JSInteropConstants
{ {
public static class JSInteropConstants public static string DeleteCookie => "deleteCookie";
{ public static string GetCookie => "getCookie";
public static string DeleteCookie => "deleteCookie"; public static string RouteOutside => "routeOutside";
public static string GetCookie => "getCookie"; public static string HideBodyOverflow => "hideBodyOverflow";
public static string RouteOutside => "routeOutside"; public static string ShowBodyOverflow => "showBodyOverflow";
public static string HideBodyOverflow => "hideBodyOverflow";
public static string ShowBodyOverflow => "showBodyOverflow";
}
} }

27
src/BlazorAdmin/JavaScript/Route.cs

@ -1,20 +1,19 @@
using Microsoft.JSInterop; using System.Threading.Tasks;
using System.Threading.Tasks; using Microsoft.JSInterop;
namespace BlazorAdmin.JavaScript namespace BlazorAdmin.JavaScript;
public class Route
{ {
public class Route private readonly IJSRuntime _jsRuntime;
{
private readonly IJSRuntime _jsRuntime;
public Route(IJSRuntime jsRuntime) public Route(IJSRuntime jsRuntime)
{ {
_jsRuntime = jsRuntime; _jsRuntime = jsRuntime;
} }
public async Task RouteOutside(string path) public async Task RouteOutside(string path)
{ {
await _jsRuntime.InvokeAsync<string>(JSInteropConstants.RouteOutside, path); await _jsRuntime.InvokeAsync<string>(JSInteropConstants.RouteOutside, path);
}
} }
} }

99
src/BlazorAdmin/Pages/CatalogItemPage/List.razor.cs

@ -1,69 +1,68 @@
using BlazorAdmin.Helpers; using System.Collections.Generic;
using System.Threading.Tasks;
using BlazorAdmin.Helpers;
using BlazorShared.Interfaces; using BlazorShared.Interfaces;
using BlazorShared.Models; using BlazorShared.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BlazorAdmin.Pages.CatalogItemPage namespace BlazorAdmin.Pages.CatalogItemPage;
public partial class List : BlazorComponent
{ {
public partial class List : BlazorComponent [Microsoft.AspNetCore.Components.Inject]
{ public ICatalogItemService CatalogItemService { get; set; }
[Microsoft.AspNetCore.Components.Inject]
public ICatalogItemService CatalogItemService { get; set; }
[Microsoft.AspNetCore.Components.Inject] [Microsoft.AspNetCore.Components.Inject]
public ICatalogLookupDataService<CatalogBrand> CatalogBrandService { get; set; } public ICatalogLookupDataService<CatalogBrand> CatalogBrandService { get; set; }
[Microsoft.AspNetCore.Components.Inject] [Microsoft.AspNetCore.Components.Inject]
public ICatalogLookupDataService<CatalogType> CatalogTypeService { get; set; } public ICatalogLookupDataService<CatalogType> CatalogTypeService { get; set; }
private List<CatalogItem> catalogItems = new List<CatalogItem>(); private List<CatalogItem> catalogItems = new List<CatalogItem>();
private List<CatalogType> catalogTypes = new List<CatalogType>(); private List<CatalogType> catalogTypes = new List<CatalogType>();
private List<CatalogBrand> catalogBrands = new List<CatalogBrand>(); private List<CatalogBrand> catalogBrands = new List<CatalogBrand>();
private Edit EditComponent { get; set; } private Edit EditComponent { get; set; }
private Delete DeleteComponent { get; set; } private Delete DeleteComponent { get; set; }
private Details DetailsComponent { get; set; } private Details DetailsComponent { get; set; }
private Create CreateComponent { get; set; } private Create CreateComponent { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{ {
if (firstRender) catalogItems = await CatalogItemService.List();
{ catalogTypes = await CatalogTypeService.List();
catalogItems = await CatalogItemService.List(); catalogBrands = await CatalogBrandService.List();
catalogTypes = await CatalogTypeService.List();
catalogBrands = await CatalogBrandService.List();
CallRequestRefresh();
}
await base.OnAfterRenderAsync(firstRender); CallRequestRefresh();
} }
private async void DetailsClick(int id) await base.OnAfterRenderAsync(firstRender);
{ }
await DetailsComponent.Open(id);
}
private async Task CreateClick() private async void DetailsClick(int id)
{ {
await CreateComponent.Open(); await DetailsComponent.Open(id);
} }
private async Task EditClick(int id) private async Task CreateClick()
{ {
await EditComponent.Open(id); await CreateComponent.Open();
} }
private async Task DeleteClick(int id) private async Task EditClick(int id)
{ {
await DeleteComponent.Open(id); await EditComponent.Open(id);
} }
private async Task ReloadCatalogItems() private async Task DeleteClick(int id)
{ {
catalogItems = await CatalogItemService.List(); await DeleteComponent.Open(id);
StateHasChanged(); }
}
private async Task ReloadCatalogItems()
{
catalogItems = await CatalogItemService.List();
StateHasChanged();
} }
} }

61
src/BlazorAdmin/Program.cs

@ -1,3 +1,6 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using BlazorAdmin.Services; using BlazorAdmin.Services;
using Blazored.LocalStorage; using Blazored.LocalStorage;
using BlazorShared; using BlazorShared;
@ -7,50 +10,46 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace BlazorAdmin namespace BlazorAdmin;
public class Program
{ {
public class Program public static async Task Main(string[] args)
{ {
public static async Task Main(string[] args) var builder = WebAssemblyHostBuilder.CreateDefault(args);
{ builder.RootComponents.Add<App>("#admin");
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#admin");
var baseUrlConfig = new BaseUrlConfiguration(); var baseUrlConfig = new BaseUrlConfiguration();
builder.Configuration.Bind(BaseUrlConfiguration.CONFIG_NAME, baseUrlConfig); builder.Configuration.Bind(BaseUrlConfiguration.CONFIG_NAME, baseUrlConfig);
builder.Services.AddScoped<BaseUrlConfiguration>(sp => baseUrlConfig); builder.Services.AddScoped<BaseUrlConfiguration>(sp => baseUrlConfig);
builder.Services.AddScoped(sp => new HttpClient() { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddScoped(sp => new HttpClient() { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<ToastService>(); builder.Services.AddScoped<ToastService>();
builder.Services.AddScoped<HttpService>(); builder.Services.AddScoped<HttpService>();
builder.Services.AddBlazoredLocalStorage(); builder.Services.AddBlazoredLocalStorage();
builder.Services.AddAuthorizationCore(); builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>(); builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
builder.Services.AddScoped(sp => (CustomAuthStateProvider)sp.GetRequiredService<AuthenticationStateProvider>()); builder.Services.AddScoped(sp => (CustomAuthStateProvider)sp.GetRequiredService<AuthenticationStateProvider>());
builder.Services.AddBlazorServices(); builder.Services.AddBlazorServices();
builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging"));
await ClearLocalStorageCache(builder.Services); await ClearLocalStorageCache(builder.Services);
await builder.Build().RunAsync(); await builder.Build().RunAsync();
} }
private static async Task ClearLocalStorageCache(IServiceCollection services) private static async Task ClearLocalStorageCache(IServiceCollection services)
{ {
var sp = services.BuildServiceProvider(); var sp = services.BuildServiceProvider();
var localStorageService = sp.GetRequiredService<ILocalStorageService>(); var localStorageService = sp.GetRequiredService<ILocalStorageService>();
await localStorageService.RemoveItemAsync(typeof(CatalogBrand).Name); await localStorageService.RemoveItemAsync(typeof(CatalogBrand).Name);
await localStorageService.RemoveItemAsync(typeof(CatalogType).Name); await localStorageService.RemoveItemAsync(typeof(CatalogType).Name);
}
} }
} }

23
src/BlazorAdmin/Services/CacheEntry.cs

@ -1,19 +1,18 @@
using System; using System;
namespace BlazorAdmin.Services namespace BlazorAdmin.Services;
public class CacheEntry<T>
{ {
public class CacheEntry<T> public CacheEntry(T item)
{
Value = item;
}
public CacheEntry()
{ {
public CacheEntry(T item)
{
Value = item;
}
public CacheEntry()
{
}
public T Value { get; set; }
public DateTime DateCreated { get; set; } = DateTime.UtcNow;
} }
public T Value { get; set; }
public DateTime DateCreated { get; set; } = DateTime.UtcNow;
} }

173
src/BlazorAdmin/Services/CachedCatalogItemServiceDecorator.cs

@ -1,114 +1,113 @@
using Blazored.LocalStorage; using System;
using BlazorShared.Interfaces;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Blazored.LocalStorage;
using BlazorShared.Interfaces;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
namespace BlazorAdmin.Services namespace BlazorAdmin.Services;
public class CachedCatalogItemServiceDecorator : ICatalogItemService
{ {
public class CachedCatalogItemServiceDecorator : ICatalogItemService private readonly ILocalStorageService _localStorageService;
{ private readonly CatalogItemService _catalogItemService;
private readonly ILocalStorageService _localStorageService; private ILogger<CachedCatalogItemServiceDecorator> _logger;
private readonly CatalogItemService _catalogItemService;
private ILogger<CachedCatalogItemServiceDecorator> _logger;
public CachedCatalogItemServiceDecorator(ILocalStorageService localStorageService, public CachedCatalogItemServiceDecorator(ILocalStorageService localStorageService,
CatalogItemService catalogItemService, CatalogItemService catalogItemService,
ILogger<CachedCatalogItemServiceDecorator> logger) ILogger<CachedCatalogItemServiceDecorator> logger)
{ {
_localStorageService = localStorageService; _localStorageService = localStorageService;
_catalogItemService = catalogItemService; _catalogItemService = catalogItemService;
_logger = logger; _logger = logger;
} }
public async Task<List<CatalogItem>> ListPaged(int pageSize) public async Task<List<CatalogItem>> ListPaged(int pageSize)
{
string key = "items";
var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<CatalogItem>>>(key);
if (cacheEntry != null)
{ {
string key = "items"; _logger.LogInformation("Loading items from local storage.");
var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<CatalogItem>>>(key); if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow)
if (cacheEntry != null)
{ {
_logger.LogInformation("Loading items from local storage."); return cacheEntry.Value;
if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow) }
{ else
return cacheEntry.Value; {
} _logger.LogInformation($"Loading {key} from local storage.");
else await _localStorageService.RemoveItemAsync(key);
{
_logger.LogInformation($"Loading {key} from local storage.");
await _localStorageService.RemoveItemAsync(key);
}
} }
var items = await _catalogItemService.ListPaged(pageSize);
var entry = new CacheEntry<List<CatalogItem>>(items);
await _localStorageService.SetItemAsync(key, entry);
return items;
} }
public async Task<List<CatalogItem>> List() var items = await _catalogItemService.ListPaged(pageSize);
var entry = new CacheEntry<List<CatalogItem>>(items);
await _localStorageService.SetItemAsync(key, entry);
return items;
}
public async Task<List<CatalogItem>> List()
{
string key = "items";
var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<CatalogItem>>>(key);
if (cacheEntry != null)
{ {
string key = "items"; _logger.LogInformation("Loading items from local storage.");
var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<CatalogItem>>>(key); if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow)
if (cacheEntry != null)
{ {
_logger.LogInformation("Loading items from local storage."); return cacheEntry.Value;
if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow) }
{ else
return cacheEntry.Value; {
} _logger.LogInformation($"Loading {key} from local storage.");
else await _localStorageService.RemoveItemAsync(key);
{
_logger.LogInformation($"Loading {key} from local storage.");
await _localStorageService.RemoveItemAsync(key);
}
} }
var items = await _catalogItemService.List();
var entry = new CacheEntry<List<CatalogItem>>(items);
await _localStorageService.SetItemAsync(key, entry);
return items;
} }
public async Task<CatalogItem> GetById(int id) var items = await _catalogItemService.List();
{ var entry = new CacheEntry<List<CatalogItem>>(items);
return (await List()).FirstOrDefault(x => x.Id == id); await _localStorageService.SetItemAsync(key, entry);
} return items;
}
public async Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem) public async Task<CatalogItem> GetById(int id)
{ {
var result = await _catalogItemService.Create(catalogItem); return (await List()).FirstOrDefault(x => x.Id == id);
await RefreshLocalStorageList(); }
return result; public async Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem)
} {
var result = await _catalogItemService.Create(catalogItem);
await RefreshLocalStorageList();
public async Task<CatalogItem> Edit(CatalogItem catalogItem) return result;
{ }
var result = await _catalogItemService.Edit(catalogItem);
await RefreshLocalStorageList();
return result; public async Task<CatalogItem> Edit(CatalogItem catalogItem)
} {
var result = await _catalogItemService.Edit(catalogItem);
await RefreshLocalStorageList();
public async Task<string> Delete(int id) return result;
{ }
var result = await _catalogItemService.Delete(id);
await RefreshLocalStorageList();
return result; public async Task<string> Delete(int id)
} {
var result = await _catalogItemService.Delete(id);
await RefreshLocalStorageList();
private async Task RefreshLocalStorageList() return result;
{ }
string key = "items";
await _localStorageService.RemoveItemAsync(key); private async Task RefreshLocalStorageList()
var items = await _catalogItemService.List(); {
var entry = new CacheEntry<List<CatalogItem>>(items); string key = "items";
await _localStorageService.SetItemAsync(key, entry);
} await _localStorageService.RemoveItemAsync(key);
var items = await _catalogItemService.List();
var entry = new CacheEntry<List<CatalogItem>>(items);
await _localStorageService.SetItemAsync(key, entry);
} }
} }

79
src/BlazorAdmin/Services/CachedCatalogLookupDataServiceDecorator .cs

@ -1,53 +1,52 @@
using Blazored.LocalStorage; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Blazored.LocalStorage;
using BlazorShared.Interfaces; using BlazorShared.Interfaces;
using BlazorShared.Models; using BlazorShared.Models;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BlazorAdmin.Services namespace BlazorAdmin.Services;
public class CachedCatalogLookupDataServiceDecorator<TLookupData, TReponse>
: ICatalogLookupDataService<TLookupData>
where TLookupData : LookupData
where TReponse : ILookupDataResponse<TLookupData>
{ {
public class CachedCatalogLookupDataServiceDecorator<TLookupData, TReponse> private readonly ILocalStorageService _localStorageService;
: ICatalogLookupDataService<TLookupData> private readonly CatalogLookupDataService<TLookupData, TReponse> _catalogTypeService;
where TLookupData : LookupData private ILogger<CachedCatalogLookupDataServiceDecorator<TLookupData, TReponse>> _logger;
where TReponse : ILookupDataResponse<TLookupData>
{
private readonly ILocalStorageService _localStorageService;
private readonly CatalogLookupDataService<TLookupData, TReponse> _catalogTypeService;
private ILogger<CachedCatalogLookupDataServiceDecorator<TLookupData, TReponse>> _logger;
public CachedCatalogLookupDataServiceDecorator(ILocalStorageService localStorageService, public CachedCatalogLookupDataServiceDecorator(ILocalStorageService localStorageService,
CatalogLookupDataService<TLookupData, TReponse> catalogTypeService, CatalogLookupDataService<TLookupData, TReponse> catalogTypeService,
ILogger<CachedCatalogLookupDataServiceDecorator<TLookupData, TReponse>> logger) ILogger<CachedCatalogLookupDataServiceDecorator<TLookupData, TReponse>> logger)
{ {
_localStorageService = localStorageService; _localStorageService = localStorageService;
_catalogTypeService = catalogTypeService; _catalogTypeService = catalogTypeService;
_logger = logger; _logger = logger;
} }
public async Task<List<TLookupData>> List() public async Task<List<TLookupData>> List()
{
string key = typeof(TLookupData).Name;
var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<TLookupData>>>(key);
if (cacheEntry != null)
{ {
string key = typeof(TLookupData).Name; _logger.LogInformation($"Loading {key} from local storage.");
var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<TLookupData>>>(key); if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow)
if (cacheEntry != null)
{ {
_logger.LogInformation($"Loading {key} from local storage."); return cacheEntry.Value;
if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow) }
{ else
return cacheEntry.Value; {
} _logger.LogInformation($"Cache expired; removing {key} from local storage.");
else await _localStorageService.RemoveItemAsync(key);
{
_logger.LogInformation($"Cache expired; removing {key} from local storage.");
await _localStorageService.RemoveItemAsync(key);
}
} }
var types = await _catalogTypeService.List();
var entry = new CacheEntry<List<TLookupData>>(types);
await _localStorageService.SetItemAsync(key, entry);
return types;
} }
var types = await _catalogTypeService.List();
var entry = new CacheEntry<List<TLookupData>>(types);
await _localStorageService.SetItemAsync(key, entry);
return types;
} }
} }

155
src/BlazorAdmin/Services/CatalogItemService.cs

@ -1,97 +1,96 @@
using BlazorShared.Interfaces; using System.Collections.Generic;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlazorShared.Interfaces;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
namespace BlazorAdmin.Services namespace BlazorAdmin.Services;
public class CatalogItemService : ICatalogItemService
{ {
public class CatalogItemService : ICatalogItemService private readonly ICatalogLookupDataService<CatalogBrand> _brandService;
private readonly ICatalogLookupDataService<CatalogType> _typeService;
private readonly HttpService _httpService;
private readonly ILogger<CatalogItemService> _logger;
public CatalogItemService(ICatalogLookupDataService<CatalogBrand> brandService,
ICatalogLookupDataService<CatalogType> typeService,
HttpService httpService,
ILogger<CatalogItemService> logger)
{ {
private readonly ICatalogLookupDataService<CatalogBrand> _brandService; _brandService = brandService;
private readonly ICatalogLookupDataService<CatalogType> _typeService; _typeService = typeService;
private readonly HttpService _httpService; _httpService = httpService;
private readonly ILogger<CatalogItemService> _logger; _logger = logger;
}
public CatalogItemService(ICatalogLookupDataService<CatalogBrand> brandService, public async Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem)
ICatalogLookupDataService<CatalogType> typeService, {
HttpService httpService, var response = await _httpService.HttpPost<CreateCatalogItemResponse>("catalog-items", catalogItem);
ILogger<CatalogItemService> logger) return response?.CatalogItem;
{ }
_brandService = brandService;
_typeService = typeService;
_httpService = httpService;
_logger = logger;
}
public async Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem) public async Task<CatalogItem> Edit(CatalogItem catalogItem)
{ {
var response = await _httpService.HttpPost<CreateCatalogItemResponse>("catalog-items", catalogItem); return (await _httpService.HttpPut<EditCatalogItemResult>("catalog-items", catalogItem)).CatalogItem;
return response?.CatalogItem; }
}
public async Task<CatalogItem> Edit(CatalogItem catalogItem) public async Task<string> Delete(int catalogItemId)
{ {
return (await _httpService.HttpPut<EditCatalogItemResult>("catalog-items", catalogItem)).CatalogItem; return (await _httpService.HttpDelete<DeleteCatalogItemResponse>("catalog-items", catalogItemId)).Status;
} }
public async Task<string> Delete(int catalogItemId) public async Task<CatalogItem> GetById(int id)
{ {
return (await _httpService.HttpDelete<DeleteCatalogItemResponse>("catalog-items", catalogItemId)).Status; var brandListTask = _brandService.List();
} var typeListTask = _typeService.List();
var itemGetTask = _httpService.HttpGet<EditCatalogItemResult>($"catalog-items/{id}");
await Task.WhenAll(brandListTask, typeListTask, itemGetTask);
var brands = brandListTask.Result;
var types = typeListTask.Result;
var catalogItem = itemGetTask.Result.CatalogItem;
catalogItem.CatalogBrand = brands.FirstOrDefault(b => b.Id == catalogItem.CatalogBrandId)?.Name;
catalogItem.CatalogType = types.FirstOrDefault(t => t.Id == catalogItem.CatalogTypeId)?.Name;
return catalogItem;
}
public async Task<CatalogItem> GetById(int id) public async Task<List<CatalogItem>> ListPaged(int pageSize)
{ {
var brandListTask = _brandService.List(); _logger.LogInformation("Fetching catalog items from API.");
var typeListTask = _typeService.List();
var itemGetTask = _httpService.HttpGet<EditCatalogItemResult>($"catalog-items/{id}");
await Task.WhenAll(brandListTask, typeListTask, itemGetTask);
var brands = brandListTask.Result;
var types = typeListTask.Result;
var catalogItem = itemGetTask.Result.CatalogItem;
catalogItem.CatalogBrand = brands.FirstOrDefault(b => b.Id == catalogItem.CatalogBrandId)?.Name;
catalogItem.CatalogType = types.FirstOrDefault(t => t.Id == catalogItem.CatalogTypeId)?.Name;
return catalogItem;
}
public async Task<List<CatalogItem>> ListPaged(int pageSize) var brandListTask = _brandService.List();
var typeListTask = _typeService.List();
var itemListTask = _httpService.HttpGet<PagedCatalogItemResponse>($"catalog-items?PageSize=10");
await Task.WhenAll(brandListTask, typeListTask, itemListTask);
var brands = brandListTask.Result;
var types = typeListTask.Result;
var items = itemListTask.Result.CatalogItems;
foreach (var item in items)
{ {
_logger.LogInformation("Fetching catalog items from API."); item.CatalogBrand = brands.FirstOrDefault(b => b.Id == item.CatalogBrandId)?.Name;
item.CatalogType = types.FirstOrDefault(t => t.Id == item.CatalogTypeId)?.Name;
var brandListTask = _brandService.List();
var typeListTask = _typeService.List();
var itemListTask = _httpService.HttpGet<PagedCatalogItemResponse>($"catalog-items?PageSize=10");
await Task.WhenAll(brandListTask, typeListTask, itemListTask);
var brands = brandListTask.Result;
var types = typeListTask.Result;
var items = itemListTask.Result.CatalogItems;
foreach (var item in items)
{
item.CatalogBrand = brands.FirstOrDefault(b => b.Id == item.CatalogBrandId)?.Name;
item.CatalogType = types.FirstOrDefault(t => t.Id == item.CatalogTypeId)?.Name;
}
return items;
} }
return items;
}
public async Task<List<CatalogItem>> List() public async Task<List<CatalogItem>> List()
{ {
_logger.LogInformation("Fetching catalog items from API."); _logger.LogInformation("Fetching catalog items from API.");
var brandListTask = _brandService.List(); var brandListTask = _brandService.List();
var typeListTask = _typeService.List(); var typeListTask = _typeService.List();
var itemListTask = _httpService.HttpGet<PagedCatalogItemResponse>($"catalog-items"); var itemListTask = _httpService.HttpGet<PagedCatalogItemResponse>($"catalog-items");
await Task.WhenAll(brandListTask, typeListTask, itemListTask); await Task.WhenAll(brandListTask, typeListTask, itemListTask);
var brands = brandListTask.Result; var brands = brandListTask.Result;
var types = typeListTask.Result; var types = typeListTask.Result;
var items = itemListTask.Result.CatalogItems; var items = itemListTask.Result.CatalogItems;
foreach (var item in items) foreach (var item in items)
{ {
item.CatalogBrand = brands.FirstOrDefault(b => b.Id == item.CatalogBrandId)?.Name; item.CatalogBrand = brands.FirstOrDefault(b => b.Id == item.CatalogBrandId)?.Name;
item.CatalogType = types.FirstOrDefault(t => t.Id == item.CatalogTypeId)?.Name; item.CatalogType = types.FirstOrDefault(t => t.Id == item.CatalogTypeId)?.Name;
}
return items;
} }
return items;
} }
} }

59
src/BlazorAdmin/Services/CatalogLookupDataService.cs

@ -1,43 +1,42 @@
using BlazorShared; using System.Collections.Generic;
using BlazorShared.Attributes;
using BlazorShared.Interfaces;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlazorShared;
using BlazorShared.Attributes;
using BlazorShared.Interfaces;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
namespace BlazorAdmin.Services;
namespace BlazorAdmin.Services public class CatalogLookupDataService<TLookupData, TReponse>
: ICatalogLookupDataService<TLookupData>
where TLookupData : LookupData
where TReponse : ILookupDataResponse<TLookupData>
{ {
public class CatalogLookupDataService<TLookupData, TReponse>
: ICatalogLookupDataService<TLookupData>
where TLookupData : LookupData
where TReponse : ILookupDataResponse<TLookupData>
{
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly ILogger<CatalogLookupDataService<TLookupData, TReponse>> _logger; private readonly ILogger<CatalogLookupDataService<TLookupData, TReponse>> _logger;
private readonly string _apiUrl; private readonly string _apiUrl;
public CatalogLookupDataService(HttpClient httpClient, public CatalogLookupDataService(HttpClient httpClient,
BaseUrlConfiguration baseUrlConfiguration, BaseUrlConfiguration baseUrlConfiguration,
ILogger<CatalogLookupDataService<TLookupData, TReponse>> logger) ILogger<CatalogLookupDataService<TLookupData, TReponse>> logger)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_logger = logger; _logger = logger;
_apiUrl = baseUrlConfiguration.ApiBase; _apiUrl = baseUrlConfiguration.ApiBase;
} }
public async Task<List<TLookupData>> List() public async Task<List<TLookupData>> List()
{ {
var endpointName = typeof(TLookupData).GetCustomAttribute<EndpointAttribute>().Name; var endpointName = typeof(TLookupData).GetCustomAttribute<EndpointAttribute>().Name;
_logger.LogInformation($"Fetching {typeof(TLookupData).Name} from API. Enpoint : {endpointName}"); _logger.LogInformation($"Fetching {typeof(TLookupData).Name} from API. Enpoint : {endpointName}");
var response = await _httpClient.GetFromJsonAsync<TReponse>($"{_apiUrl}{endpointName}"); var response = await _httpClient.GetFromJsonAsync<TReponse>($"{_apiUrl}{endpointName}");
return response.List; return response.List;
}
} }
} }

135
src/BlazorAdmin/Services/HttpService.cs

@ -1,96 +1,95 @@
using BlazorShared; using System.Net.Http;
using BlazorShared.Models;
using System.Net.Http;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlazorShared;
using BlazorShared.Models;
namespace BlazorAdmin.Services;
namespace BlazorAdmin.Services public class HttpService
{ {
public class HttpService private readonly HttpClient _httpClient;
{ private readonly ToastService _toastService;
private readonly HttpClient _httpClient; private readonly string _apiUrl;
private readonly ToastService _toastService;
private readonly string _apiUrl;
public HttpService(HttpClient httpClient, BaseUrlConfiguration baseUrlConfiguration, ToastService toastService)
{
_httpClient = httpClient;
_toastService = toastService;
_apiUrl = baseUrlConfiguration.ApiBase;
}
public HttpService(HttpClient httpClient, BaseUrlConfiguration baseUrlConfiguration, ToastService toastService) public async Task<T> HttpGet<T>(string uri)
where T : class
{
var result = await _httpClient.GetAsync($"{_apiUrl}{uri}");
if (!result.IsSuccessStatusCode)
{ {
_httpClient = httpClient; return null;
_toastService = toastService;
_apiUrl = baseUrlConfiguration.ApiBase;
} }
public async Task<T> HttpGet<T>(string uri) return await FromHttpResponseMessage<T>(result);
where T : class }
{
var result = await _httpClient.GetAsync($"{_apiUrl}{uri}");
if (!result.IsSuccessStatusCode)
{
return null;
}
return await FromHttpResponseMessage<T>(result); public async Task<T> HttpDelete<T>(string uri, int id)
where T : class
{
var result = await _httpClient.DeleteAsync($"{_apiUrl}{uri}/{id}");
if (!result.IsSuccessStatusCode)
{
return null;
} }
public async Task<T> HttpDelete<T>(string uri, int id) return await FromHttpResponseMessage<T>(result);
where T : class }
{
var result = await _httpClient.DeleteAsync($"{_apiUrl}{uri}/{id}");
if (!result.IsSuccessStatusCode)
{
return null;
}
return await FromHttpResponseMessage<T>(result); public async Task<T> HttpPost<T>(string uri, object dataToSend)
} where T : class
{
var content = ToJson(dataToSend);
public async Task<T> HttpPost<T>(string uri, object dataToSend) var result = await _httpClient.PostAsync($"{_apiUrl}{uri}", content);
where T : class if (!result.IsSuccessStatusCode)
{ {
var content = ToJson(dataToSend); var exception = JsonSerializer.Deserialize<ErrorDetails>(await result.Content.ReadAsStringAsync(), new JsonSerializerOptions
var result = await _httpClient.PostAsync($"{_apiUrl}{uri}", content);
if (!result.IsSuccessStatusCode)
{ {
var exception = JsonSerializer.Deserialize<ErrorDetails>(await result.Content.ReadAsStringAsync(), new JsonSerializerOptions PropertyNameCaseInsensitive = true
{ });
PropertyNameCaseInsensitive = true _toastService.ShowToast($"Error : {exception.Message}", ToastLevel.Error);
});
_toastService.ShowToast($"Error : {exception.Message}", ToastLevel.Error);
return null;
}
return await FromHttpResponseMessage<T>(result); return null;
} }
public async Task<T> HttpPut<T>(string uri, object dataToSend) return await FromHttpResponseMessage<T>(result);
where T : class }
{
var content = ToJson(dataToSend);
var result = await _httpClient.PutAsync($"{_apiUrl}{uri}", content);
if (!result.IsSuccessStatusCode)
{
_toastService.ShowToast("Error", ToastLevel.Error);
return null;
}
return await FromHttpResponseMessage<T>(result); public async Task<T> HttpPut<T>(string uri, object dataToSend)
} where T : class
{
var content = ToJson(dataToSend);
private StringContent ToJson(object obj) var result = await _httpClient.PutAsync($"{_apiUrl}{uri}", content);
if (!result.IsSuccessStatusCode)
{ {
return new StringContent(JsonSerializer.Serialize(obj), Encoding.UTF8, "application/json"); _toastService.ShowToast("Error", ToastLevel.Error);
return null;
} }
private async Task<T> FromHttpResponseMessage<T>(HttpResponseMessage result) return await FromHttpResponseMessage<T>(result);
}
private StringContent ToJson(object obj)
{
return new StringContent(JsonSerializer.Serialize(obj), Encoding.UTF8, "application/json");
}
private async Task<T> FromHttpResponseMessage<T>(HttpResponseMessage result)
{
return JsonSerializer.Deserialize<T>(await result.Content.ReadAsStringAsync(), new JsonSerializerOptions
{ {
return JsonSerializer.Deserialize<T>(await result.Content.ReadAsStringAsync(), new JsonSerializerOptions PropertyNameCaseInsensitive = true
{ });
PropertyNameCaseInsensitive = true
});
}
} }
} }

79
src/BlazorAdmin/Services/ToastService.cs

@ -1,55 +1,54 @@
using System; using System;
using System.Timers; using System.Timers;
namespace BlazorAdmin.Services namespace BlazorAdmin.Services;
public enum ToastLevel
{
Info,
Success,
Warning,
Error
}
public class ToastService : IDisposable
{ {
public enum ToastLevel public event Action<string, ToastLevel> OnShow;
public event Action OnHide;
private Timer Countdown;
public void ShowToast(string message, ToastLevel level)
{ {
Info, OnShow?.Invoke(message, level);
Success, StartCountdown();
Warning,
Error
} }
private void StartCountdown()
public class ToastService : IDisposable
{ {
public event Action<string, ToastLevel> OnShow; SetCountdown();
public event Action OnHide; if (Countdown.Enabled)
private Timer Countdown;
public void ShowToast(string message, ToastLevel level)
{
OnShow?.Invoke(message, level);
StartCountdown();
}
private void StartCountdown()
{ {
SetCountdown(); Countdown.Stop();
if (Countdown.Enabled) Countdown.Start();
{
Countdown.Stop();
Countdown.Start();
}
else
{
Countdown.Start();
}
} }
private void SetCountdown() else
{ {
if (Countdown == null) Countdown.Start();
{
Countdown = new Timer(3000);
Countdown.Elapsed += HideToast;
Countdown.AutoReset = false;
}
} }
private void HideToast(object source, ElapsedEventArgs args) }
{ private void SetCountdown()
OnHide?.Invoke(); {
} if (Countdown == null)
public void Dispose()
{ {
Countdown?.Dispose(); Countdown = new Timer(3000);
Countdown.Elapsed += HideToast;
Countdown.AutoReset = false;
} }
} }
private void HideToast(object source, ElapsedEventArgs args)
{
OnHide?.Invoke();
}
public void Dispose()
{
Countdown?.Dispose();
}
} }

23
src/BlazorAdmin/ServicesConfiguration.cs

@ -3,20 +3,19 @@ using BlazorShared.Interfaces;
using BlazorShared.Models; using BlazorShared.Models;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace BlazorAdmin namespace BlazorAdmin;
public static class ServicesConfiguration
{ {
public static class ServicesConfiguration public static IServiceCollection AddBlazorServices(this IServiceCollection services)
{ {
public static IServiceCollection AddBlazorServices(this IServiceCollection services) services.AddScoped<ICatalogLookupDataService<CatalogBrand>, CachedCatalogLookupDataServiceDecorator<CatalogBrand, CatalogBrandResponse>>();
{ services.AddScoped<CatalogLookupDataService<CatalogBrand, CatalogBrandResponse>>();
services.AddScoped<ICatalogLookupDataService<CatalogBrand>, CachedCatalogLookupDataServiceDecorator<CatalogBrand,CatalogBrandResponse>>(); services.AddScoped<ICatalogLookupDataService<CatalogType>, CachedCatalogLookupDataServiceDecorator<CatalogType, CatalogTypeResponse>>();
services.AddScoped<CatalogLookupDataService<CatalogBrand, CatalogBrandResponse>>(); services.AddScoped<CatalogLookupDataService<CatalogType, CatalogTypeResponse>>();
services.AddScoped<ICatalogLookupDataService<CatalogType>, CachedCatalogLookupDataServiceDecorator<CatalogType,CatalogTypeResponse>>(); services.AddScoped<ICatalogItemService, CachedCatalogItemServiceDecorator>();
services.AddScoped<CatalogLookupDataService<CatalogType, CatalogTypeResponse>>(); services.AddScoped<CatalogItemService>();
services.AddScoped<ICatalogItemService, CachedCatalogItemServiceDecorator>();
services.AddScoped<CatalogItemService>();
return services; return services;
}
} }
} }

49
src/BlazorAdmin/Shared/CustomInputSelect.cs

@ -1,38 +1,37 @@
using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Forms;
namespace BlazorAdmin.Shared namespace BlazorAdmin.Shared;
/// <summary>
/// This is needed until 5.0 ships with native support
/// https://www.pragimtech.com/blog/blazor/inputselect-does-not-support-system.int32/
/// </summary>
/// <typeparam name="TValue"></typeparam>
public class CustomInputSelect<TValue> : InputSelect<TValue>
{ {
/// <summary> protected override bool TryParseValueFromString(string value, out TValue result,
/// This is needed until 5.0 ships with native support out string validationErrorMessage)
/// https://www.pragimtech.com/blog/blazor/inputselect-does-not-support-system.int32/
/// </summary>
/// <typeparam name="TValue"></typeparam>
public class CustomInputSelect<TValue> : InputSelect<TValue>
{ {
protected override bool TryParseValueFromString(string value, out TValue result, if (typeof(TValue) == typeof(int))
out string validationErrorMessage)
{ {
if (typeof(TValue) == typeof(int)) if (int.TryParse(value, out var resultInt))
{ {
if (int.TryParse(value, out var resultInt)) result = (TValue)(object)resultInt;
{ validationErrorMessage = null;
result = (TValue)(object)resultInt; return true;
validationErrorMessage = null;
return true;
}
else
{
result = default;
validationErrorMessage =
$"The selected value {value} is not a valid number.";
return false;
}
} }
else else
{ {
return base.TryParseValueFromString(value, out result, result = default;
out validationErrorMessage); validationErrorMessage =
$"The selected value {value} is not a valid number.";
return false;
} }
} }
else
{
return base.TryParseValueFromString(value, out result,
out validationErrorMessage);
}
} }
} }

9
src/BlazorShared/Attributes/EndpointAttribute.cs

@ -1,9 +1,8 @@
using System; using System;
namespace BlazorShared.Attributes namespace BlazorShared.Attributes;
public class EndpointAttribute : Attribute
{ {
public class EndpointAttribute : Attribute public string Name { get; set; }
{
public string Name { get; set; }
}
} }

25
src/BlazorShared/Authorization/ClaimValue.cs

@ -1,18 +1,17 @@
namespace BlazorShared.Authorization namespace BlazorShared.Authorization;
public class ClaimValue
{ {
public class ClaimValue public ClaimValue()
{ {
public ClaimValue() }
{
}
public ClaimValue(string type, string value)
{
Type = type;
Value = value;
}
public string Type { get; set; } public ClaimValue(string type, string value)
public string Value { get; set; } {
Type = type;
Value = value;
} }
public string Type { get; set; }
public string Value { get; set; }
} }

11
src/BlazorShared/Authorization/Constants.cs

@ -1,10 +1,9 @@
namespace BlazorShared.Authorization namespace BlazorShared.Authorization;
public static class Constants
{ {
public static class Constants public static class Roles
{ {
public static class Roles public const string ADMINISTRATORS = "Administrators";
{
public const string ADMINISTRATORS = "Administrators";
}
} }
} }

19
src/BlazorShared/Authorization/UserInfo.cs

@ -1,14 +1,13 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace BlazorShared.Authorization namespace BlazorShared.Authorization;
public class UserInfo
{ {
public class UserInfo public static readonly UserInfo Anonymous = new UserInfo();
{ public bool IsAuthenticated { get; set; }
public static readonly UserInfo Anonymous = new UserInfo(); public string NameClaimType { get; set; }
public bool IsAuthenticated { get; set; } public string RoleClaimType { get; set; }
public string NameClaimType { get; set; } public string Token { get; set; }
public string RoleClaimType { get; set; } public IEnumerable<ClaimValue> Claims { get; set; }
public string Token { get; set; }
public IEnumerable<ClaimValue> Claims { get; set; }
}
} }

13
src/BlazorShared/BaseUrlConfiguration.cs

@ -1,10 +1,9 @@
namespace BlazorShared namespace BlazorShared;
public class BaseUrlConfiguration
{ {
public class BaseUrlConfiguration public const string CONFIG_NAME = "baseUrls";
{
public const string CONFIG_NAME = "baseUrls";
public string ApiBase { get; set; } public string ApiBase { get; set; }
public string WebBase { get; set; } public string WebBase { get; set; }
}
} }

2
src/BlazorShared/BlazorShared.csproj

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

19
src/BlazorShared/Interfaces/ICatalogItemService.cs

@ -2,15 +2,14 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using BlazorShared.Models; using BlazorShared.Models;
namespace BlazorShared.Interfaces namespace BlazorShared.Interfaces;
public interface ICatalogItemService
{ {
public interface ICatalogItemService Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem);
{ Task<CatalogItem> Edit(CatalogItem catalogItem);
Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem); Task<string> Delete(int id);
Task<CatalogItem> Edit(CatalogItem catalogItem); Task<CatalogItem> GetById(int id);
Task<string> Delete(int id); Task<List<CatalogItem>> ListPaged(int pageSize);
Task<CatalogItem> GetById(int id); Task<List<CatalogItem>> List();
Task<List<CatalogItem>> ListPaged(int pageSize);
Task<List<CatalogItem>> List();
}
} }

13
src/BlazorShared/Interfaces/ICatalogLookupDataService.cs

@ -1,11 +1,10 @@
using BlazorShared.Models; using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlazorShared.Models;
namespace BlazorShared.Interfaces namespace BlazorShared.Interfaces;
public interface ICatalogLookupDataService<TLookupData> where TLookupData : LookupData
{ {
public interface ICatalogLookupDataService<TLookupData> where TLookupData : LookupData Task<List<TLookupData>> List();
{
Task<List<TLookupData>> List();
}
} }

13
src/BlazorShared/Interfaces/ILookupDataResponse.cs

@ -1,10 +1,9 @@
using BlazorShared.Models; using System.Collections.Generic;
using System.Collections.Generic; using BlazorShared.Models;
namespace BlazorShared.Interfaces namespace BlazorShared.Interfaces;
public interface ILookupDataResponse<TLookupData> where TLookupData : LookupData
{ {
public interface ILookupDataResponse<TLookupData> where TLookupData : LookupData List<TLookupData> List { get; set; }
{
List<TLookupData> List { get; set; }
}
} }

9
src/BlazorShared/Models/CatalogBrand.cs

@ -1,9 +1,8 @@
using BlazorShared.Attributes; using BlazorShared.Attributes;
namespace BlazorShared.Models namespace BlazorShared.Models;
[Endpoint(Name = "catalog-brands")]
public class CatalogBrand : LookupData
{ {
[Endpoint(Name = "catalog-brands")]
public class CatalogBrand : LookupData
{
}
} }

15
src/BlazorShared/Models/CatalogBrandResponse.cs

@ -1,12 +1,11 @@
using BlazorShared.Interfaces; using System.Collections.Generic;
using System.Collections.Generic;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using BlazorShared.Interfaces;
namespace BlazorShared.Models namespace BlazorShared.Models;
public class CatalogBrandResponse : ILookupDataResponse<CatalogBrand>
{ {
public class CatalogBrandResponse : ILookupDataResponse<CatalogBrand> [JsonPropertyName("CatalogBrands")]
{ public List<CatalogBrand> List { get; set; } = new List<CatalogBrand>();
[JsonPropertyName("CatalogBrands")]
public List<CatalogBrand> List { get; set; } = new List<CatalogBrand>();
}
} }

117
src/BlazorShared/Models/CatalogItem.cs

@ -4,85 +4,84 @@ using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlazorInputFile; using BlazorInputFile;
namespace BlazorShared.Models namespace BlazorShared.Models;
public class CatalogItem
{ {
public class CatalogItem public int Id { get; set; }
{
public int Id { get; set; }
public int CatalogTypeId { get; set; } public int CatalogTypeId { get; set; }
public string CatalogType { get; set; } = "NotSet"; public string CatalogType { get; set; } = "NotSet";
public int CatalogBrandId { get; set; } public int CatalogBrandId { get; set; }
public string CatalogBrand { get; set; } = "NotSet"; public string CatalogBrand { get; set; } = "NotSet";
[Required(ErrorMessage = "The Name field is required")] [Required(ErrorMessage = "The Name field is required")]
public string Name { get; set; } public string Name { get; set; }
[Required(ErrorMessage = "The Description field is required")] [Required(ErrorMessage = "The Description field is required")]
public string Description { get; set; } public string Description { get; set; }
// decimal(18,2) // decimal(18,2)
[RegularExpression(@"^\d+(\.\d{0,2})*$", ErrorMessage = "The field Price must be a positive number with maximum two decimals.")] [RegularExpression(@"^\d+(\.\d{0,2})*$", ErrorMessage = "The field Price must be a positive number with maximum two decimals.")]
[Range(0.01, 1000)] [Range(0.01, 1000)]
[DataType(DataType.Currency)] [DataType(DataType.Currency)]
public decimal Price { get; set; } public decimal Price { get; set; }
public string PictureUri { get; set; } public string PictureUri { get; set; }
public string PictureBase64 { get; set; } public string PictureBase64 { get; set; }
public string PictureName { get; set; } public string PictureName { get; set; }
private const int ImageMaximumBytes = 512000; private const int ImageMaximumBytes = 512000;
public static string IsValidImage(string pictureName, string pictureBase64) public static string IsValidImage(string pictureName, string pictureBase64)
{
if (string.IsNullOrEmpty(pictureBase64))
{ {
if (string.IsNullOrEmpty(pictureBase64)) return "File not found!";
{ }
return "File not found!"; var fileData = Convert.FromBase64String(pictureBase64);
}
var fileData = Convert.FromBase64String(pictureBase64);
if (fileData.Length <= 0)
{
return "File length is 0!";
}
if (fileData.Length > ImageMaximumBytes) if (fileData.Length <= 0)
{ {
return "Maximum length is 512KB"; return "File length is 0!";
} }
if (!IsExtensionValid(pictureName)) if (fileData.Length > ImageMaximumBytes)
{ {
return "File is not image"; return "Maximum length is 512KB";
} }
return null; if (!IsExtensionValid(pictureName))
{
return "File is not image";
} }
public static async Task<string> DataToBase64(IFileListEntry fileItem) return null;
}
public static async Task<string> DataToBase64(IFileListEntry fileItem)
{
using (var reader = new StreamReader(fileItem.Data))
{ {
using ( var reader = new StreamReader(fileItem.Data)) using (var memStream = new MemoryStream())
{ {
using (var memStream = new MemoryStream()) await reader.BaseStream.CopyToAsync(memStream);
{ var fileData = memStream.ToArray();
await reader.BaseStream.CopyToAsync(memStream); var encodedBase64 = Convert.ToBase64String(fileData);
var fileData = memStream.ToArray();
var encodedBase64 = Convert.ToBase64String(fileData); return encodedBase64;
return encodedBase64;
}
} }
} }
}
private static bool IsExtensionValid(string fileName) private static bool IsExtensionValid(string fileName)
{ {
var extension = Path.GetExtension(fileName); var extension = Path.GetExtension(fileName);
return string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) || return string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) ||
string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase) ||
string.Equals(extension, ".gif", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".gif", StringComparison.OrdinalIgnoreCase) ||
string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase); string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase);
}
} }
} }

9
src/BlazorShared/Models/CatalogType.cs

@ -1,9 +1,8 @@
using BlazorShared.Attributes; using BlazorShared.Attributes;
namespace BlazorShared.Models namespace BlazorShared.Models;
[Endpoint(Name = "catalog-types")]
public class CatalogType : LookupData
{ {
[Endpoint(Name = "catalog-types")]
public class CatalogType : LookupData
{
}
} }

15
src/BlazorShared/Models/CatalogTypeResponse.cs

@ -1,13 +1,12 @@
using BlazorShared.Interfaces; using System.Collections.Generic;
using System.Collections.Generic;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using BlazorShared.Interfaces;
namespace BlazorShared.Models namespace BlazorShared.Models;
public class CatalogTypeResponse : ILookupDataResponse<CatalogType>
{ {
public class CatalogTypeResponse : ILookupDataResponse<CatalogType>
{
[JsonPropertyName("CatalogTypes")] [JsonPropertyName("CatalogTypes")]
public List<CatalogType> List { get; set; } = new List<CatalogType>(); public List<CatalogType> List { get; set; } = new List<CatalogType>();
}
} }

35
src/BlazorShared/Models/CreateCatalogItemRequest.cs

@ -1,28 +1,27 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace BlazorShared.Models namespace BlazorShared.Models;
public class CreateCatalogItemRequest
{ {
public class CreateCatalogItemRequest public int CatalogTypeId { get; set; }
{
public int CatalogTypeId { get; set; }
public int CatalogBrandId { get; set; } public int CatalogBrandId { get; set; }
[Required(ErrorMessage = "The Name field is required")] [Required(ErrorMessage = "The Name field is required")]
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "The Description field is required")] [Required(ErrorMessage = "The Description field is required")]
public string Description { get; set; } = string.Empty; public string Description { get; set; } = string.Empty;
// decimal(18,2) // decimal(18,2)
[RegularExpression(@"^\d+(\.\d{0,2})*$", ErrorMessage = "The field Price must be a positive number with maximum two decimals.")] [RegularExpression(@"^\d+(\.\d{0,2})*$", ErrorMessage = "The field Price must be a positive number with maximum two decimals.")]
[Range(0.01, 1000)] [Range(0.01, 1000)]
[DataType(DataType.Currency)] [DataType(DataType.Currency)]
public decimal Price { get; set; } = 0; public decimal Price { get; set; } = 0;
public string PictureUri { get; set; } = string.Empty; public string PictureUri { get; set; } = string.Empty;
public string PictureBase64 { get; set; } = string.Empty; public string PictureBase64 { get; set; } = string.Empty;
public string PictureName { get; set; } = string.Empty; public string PictureName { get; set; } = string.Empty;
}
} }

9
src/BlazorShared/Models/CreateCatalogItemResponse.cs

@ -1,7 +1,6 @@
namespace BlazorShared.Models namespace BlazorShared.Models;
public class CreateCatalogItemResponse
{ {
public class CreateCatalogItemResponse public CatalogItem CatalogItem { get; set; } = new CatalogItem();
{
public CatalogItem CatalogItem { get; set; } = new CatalogItem();
}
} }

9
src/BlazorShared/Models/DeleteCatalogItemResponse.cs

@ -1,7 +1,6 @@
namespace BlazorShared.Models namespace BlazorShared.Models;
public class DeleteCatalogItemResponse
{ {
public class DeleteCatalogItemResponse public string Status { get; set; } = "Deleted";
{
public string Status { get; set; } = "Deleted";
}
} }

9
src/BlazorShared/Models/EditCatalogItemResponse.cs

@ -1,7 +1,6 @@
namespace BlazorShared.Models namespace BlazorShared.Models;
public class EditCatalogItemResult
{ {
public class EditCatalogItemResult public CatalogItem CatalogItem { get; set; } = new CatalogItem();
{
public CatalogItem CatalogItem { get; set; } = new CatalogItem();
}
} }

15
src/BlazorShared/Models/ErrorDetails.cs

@ -1,14 +1,13 @@
using System.Text.Json; using System.Text.Json;
namespace BlazorShared.Models namespace BlazorShared.Models;
public class ErrorDetails
{ {
public class ErrorDetails public int StatusCode { get; set; }
public string Message { get; set; }
public override string ToString()
{ {
public int StatusCode { get; set; } return JsonSerializer.Serialize(this);
public string Message { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
} }
} }

11
src/BlazorShared/Models/LookupData.cs

@ -1,8 +1,7 @@
namespace BlazorShared.Models namespace BlazorShared.Models;
public abstract class LookupData
{ {
public abstract class LookupData public int Id { get; set; }
{ public string Name { get; set; }
public int Id { get; set; }
public string Name { get; set; }
}
} }

11
src/BlazorShared/Models/PagedCatalogItemResponse.cs

@ -1,10 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace BlazorShared.Models namespace BlazorShared.Models;
public class PagedCatalogItemResponse
{ {
public class PagedCatalogItemResponse public List<CatalogItem> CatalogItems { get; set; } = new List<CatalogItem>();
{ public int PageCount { get; set; } = 0;
public List<CatalogItem> CatalogItems { get; set; } = new List<CatalogItem>();
public int PageCount { get; set; } = 0;
}
} }

37
src/Infrastructure/Data/BasketQueryService.cs

@ -1,27 +1,26 @@
using Microsoft.EntityFrameworkCore; using System.Linq;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
namespace Microsoft.eShopWeb.Infrastructure.Data namespace Microsoft.eShopWeb.Infrastructure.Data;
public class BasketQueryService : IBasketQueryService
{ {
public class BasketQueryService : IBasketQueryService private readonly CatalogContext _dbContext;
{
private readonly CatalogContext _dbContext;
public BasketQueryService(CatalogContext dbContext) public BasketQueryService(CatalogContext dbContext)
{ {
_dbContext = dbContext; _dbContext = dbContext;
} }
public async Task<int> CountTotalBasketItems(string username) public async Task<int> CountTotalBasketItems(string username)
{ {
var totalItems = await _dbContext.Baskets var totalItems = await _dbContext.Baskets
.Where(basket => basket.BuyerId == username) .Where(basket => basket.BuyerId == username)
.SelectMany(item => item.Items) .SelectMany(item => item.Items)
.SumAsync(sum => sum.Quantity); .SumAsync(sum => sum.Quantity);
return totalItems; return totalItems;
}
} }
} }

38
src/Infrastructure/Data/CatalogContext.cs

@ -1,30 +1,28 @@
using Microsoft.EntityFrameworkCore; using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
using System.Reflection;
namespace Microsoft.eShopWeb.Infrastructure.Data namespace Microsoft.eShopWeb.Infrastructure.Data;
{
public class CatalogContext : DbContext public class CatalogContext : DbContext
{
public CatalogContext(DbContextOptions<CatalogContext> options) : base(options)
{ {
public CatalogContext(DbContextOptions<CatalogContext> options) : base(options) }
{
}
public DbSet<Basket> Baskets { get; set; } public DbSet<Basket> Baskets { get; set; }
public DbSet<CatalogItem> CatalogItems { get; set; } public DbSet<CatalogItem> CatalogItems { get; set; }
public DbSet<CatalogBrand> CatalogBrands { get; set; } public DbSet<CatalogBrand> CatalogBrands { get; set; }
public DbSet<CatalogType> CatalogTypes { get; set; } public DbSet<CatalogType> CatalogTypes { get; set; }
public DbSet<Order> Orders { get; set; } public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; } public DbSet<OrderItem> OrderItems { get; set; }
public DbSet<BasketItem> BasketItems { get; set; } public DbSet<BasketItem> BasketItems { get; set; }
protected override void OnModelCreating(ModelBuilder builder) protected override void OnModelCreating(ModelBuilder builder)
{ {
base.OnModelCreating(builder); base.OnModelCreating(builder);
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
} }
} }

105
src/Infrastructure/Data/CatalogContextSeed.cs

@ -1,64 +1,64 @@
using Microsoft.EntityFrameworkCore; using System;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.Extensions.Logging;
namespace Microsoft.eShopWeb.Infrastructure.Data namespace Microsoft.eShopWeb.Infrastructure.Data;
public class CatalogContextSeed
{ {
public class CatalogContextSeed public static async Task SeedAsync(CatalogContext catalogContext,
ILoggerFactory loggerFactory, int retry = 0)
{ {
public static async Task SeedAsync(CatalogContext catalogContext, var retryForAvailability = retry;
ILoggerFactory loggerFactory, int retry = 0) try
{ {
var retryForAvailability = retry; if (catalogContext.Database.IsSqlServer())
try
{ {
if (catalogContext.Database.IsSqlServer()) catalogContext.Database.Migrate();
{ }
catalogContext.Database.Migrate();
}
if (!await catalogContext.CatalogBrands.AnyAsync())
{
await catalogContext.CatalogBrands.AddRangeAsync(
GetPreconfiguredCatalogBrands());
await catalogContext.SaveChangesAsync();
}
if (!await catalogContext.CatalogTypes.AnyAsync()) if (!await catalogContext.CatalogBrands.AnyAsync())
{ {
await catalogContext.CatalogTypes.AddRangeAsync( await catalogContext.CatalogBrands.AddRangeAsync(
GetPreconfiguredCatalogTypes()); GetPreconfiguredCatalogBrands());
await catalogContext.SaveChangesAsync(); await catalogContext.SaveChangesAsync();
} }
if (!await catalogContext.CatalogItems.AnyAsync()) if (!await catalogContext.CatalogTypes.AnyAsync())
{ {
await catalogContext.CatalogItems.AddRangeAsync( await catalogContext.CatalogTypes.AddRangeAsync(
GetPreconfiguredItems()); GetPreconfiguredCatalogTypes());
await catalogContext.SaveChangesAsync(); await catalogContext.SaveChangesAsync();
}
} }
catch (Exception ex)
if (!await catalogContext.CatalogItems.AnyAsync())
{ {
if (retryForAvailability >= 10) throw; await catalogContext.CatalogItems.AddRangeAsync(
GetPreconfiguredItems());
retryForAvailability++; await catalogContext.SaveChangesAsync();
var log = loggerFactory.CreateLogger<CatalogContextSeed>();
log.LogError(ex.Message);
await SeedAsync(catalogContext, loggerFactory, retryForAvailability);
throw;
} }
} }
catch (Exception ex)
static IEnumerable<CatalogBrand> GetPreconfiguredCatalogBrands()
{ {
return new List<CatalogBrand> if (retryForAvailability >= 10) throw;
retryForAvailability++;
var log = loggerFactory.CreateLogger<CatalogContextSeed>();
log.LogError(ex.Message);
await SeedAsync(catalogContext, loggerFactory, retryForAvailability);
throw;
}
}
static IEnumerable<CatalogBrand> GetPreconfiguredCatalogBrands()
{
return new List<CatalogBrand>
{ {
new("Azure"), new("Azure"),
new(".NET"), new(".NET"),
@ -66,22 +66,22 @@ namespace Microsoft.eShopWeb.Infrastructure.Data
new("SQL Server"), new("SQL Server"),
new("Other") new("Other")
}; };
} }
static IEnumerable<CatalogType> GetPreconfiguredCatalogTypes() static IEnumerable<CatalogType> GetPreconfiguredCatalogTypes()
{ {
return new List<CatalogType> return new List<CatalogType>
{ {
new("Mug"), new("Mug"),
new("T-Shirt"), new("T-Shirt"),
new("Sheet"), new("Sheet"),
new("USB Memory Stick") new("USB Memory Stick")
}; };
} }
static IEnumerable<CatalogItem> GetPreconfiguredItems() static IEnumerable<CatalogItem> GetPreconfiguredItems()
{ {
return new List<CatalogItem> return new List<CatalogItem>
{ {
new(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"), new(2,2, ".NET Bot Black Sweatshirt", ".NET Bot Black Sweatshirt", 19.5M, "http://catalogbaseurltobereplaced/images/products/1.png"),
new(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"), new(1,2, ".NET Black & White Mug", ".NET Black & White Mug", 8.50M, "http://catalogbaseurltobereplaced/images/products/2.png"),
@ -96,6 +96,5 @@ namespace Microsoft.eShopWeb.Infrastructure.Data
new(3,2, "Cup<T> Sheet", "Cup<T> Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"), new(3,2, "Cup<T> Sheet", "Cup<T> Sheet", 8.5M, "http://catalogbaseurltobereplaced/images/products/11.png"),
new(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png") new(2,5, "Prism White TShirt", "Prism White TShirt", 12, "http://catalogbaseurltobereplaced/images/products/12.png")
}; };
}
} }
} }

19
src/Infrastructure/Data/Config/BasketConfiguration.cs

@ -2,18 +2,17 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
namespace Microsoft.eShopWeb.Infrastructure.Data.Config namespace Microsoft.eShopWeb.Infrastructure.Data.Config;
public class BasketConfiguration : IEntityTypeConfiguration<Basket>
{ {
public class BasketConfiguration : IEntityTypeConfiguration<Basket> public void Configure(EntityTypeBuilder<Basket> builder)
{ {
public void Configure(EntityTypeBuilder<Basket> builder) var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items));
{ navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items));
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
builder.Property(b => b.BuyerId) builder.Property(b => b.BuyerId)
.IsRequired() .IsRequired()
.HasMaxLength(256); .HasMaxLength(256);
}
} }
} }

15
src/Infrastructure/Data/Config/BasketItemConfiguration.cs

@ -2,15 +2,14 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
namespace Microsoft.eShopWeb.Infrastructure.Data.Config namespace Microsoft.eShopWeb.Infrastructure.Data.Config;
public class BasketItemConfiguration : IEntityTypeConfiguration<BasketItem>
{ {
public class BasketItemConfiguration : IEntityTypeConfiguration<BasketItem> public void Configure(EntityTypeBuilder<BasketItem> builder)
{ {
public void Configure(EntityTypeBuilder<BasketItem> builder) builder.Property(bi => bi.UnitPrice)
{ .IsRequired(true)
builder.Property(bi => bi.UnitPrice) .HasColumnType("decimal(18,2)");
.IsRequired(true)
.HasColumnType("decimal(18,2)");
}
} }
} }

23
src/Infrastructure/Data/Config/CatalogBrandConfiguration.cs

@ -2,21 +2,20 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Entities;
namespace Microsoft.eShopWeb.Infrastructure.Data.Config namespace Microsoft.eShopWeb.Infrastructure.Data.Config;
public class CatalogBrandConfiguration : IEntityTypeConfiguration<CatalogBrand>
{ {
public class CatalogBrandConfiguration : IEntityTypeConfiguration<CatalogBrand> public void Configure(EntityTypeBuilder<CatalogBrand> builder)
{ {
public void Configure(EntityTypeBuilder<CatalogBrand> builder) builder.HasKey(ci => ci.Id);
{
builder.HasKey(ci => ci.Id);
builder.Property(ci => ci.Id) builder.Property(ci => ci.Id)
.UseHiLo("catalog_brand_hilo") .UseHiLo("catalog_brand_hilo")
.IsRequired(); .IsRequired();
builder.Property(cb => cb.Brand) builder.Property(cb => cb.Brand)
.IsRequired() .IsRequired()
.HasMaxLength(100); .HasMaxLength(100);
}
} }
} }

45
src/Infrastructure/Data/Config/CatalogItemConfiguration.cs

@ -2,36 +2,35 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Entities;
namespace Microsoft.eShopWeb.Infrastructure.Data.Config namespace Microsoft.eShopWeb.Infrastructure.Data.Config;
public class CatalogItemConfiguration : IEntityTypeConfiguration<CatalogItem>
{ {
public class CatalogItemConfiguration : IEntityTypeConfiguration<CatalogItem> public void Configure(EntityTypeBuilder<CatalogItem> builder)
{ {
public void Configure(EntityTypeBuilder<CatalogItem> builder) builder.ToTable("Catalog");
{
builder.ToTable("Catalog");
builder.Property(ci => ci.Id) builder.Property(ci => ci.Id)
.UseHiLo("catalog_hilo") .UseHiLo("catalog_hilo")
.IsRequired(); .IsRequired();
builder.Property(ci => ci.Name) builder.Property(ci => ci.Name)
.IsRequired(true) .IsRequired(true)
.HasMaxLength(50); .HasMaxLength(50);
builder.Property(ci => ci.Price) builder.Property(ci => ci.Price)
.IsRequired(true) .IsRequired(true)
.HasColumnType("decimal(18,2)"); .HasColumnType("decimal(18,2)");
builder.Property(ci => ci.PictureUri) builder.Property(ci => ci.PictureUri)
.IsRequired(false); .IsRequired(false);
builder.HasOne(ci => ci.CatalogBrand) builder.HasOne(ci => ci.CatalogBrand)
.WithMany() .WithMany()
.HasForeignKey(ci => ci.CatalogBrandId); .HasForeignKey(ci => ci.CatalogBrandId);
builder.HasOne(ci => ci.CatalogType) builder.HasOne(ci => ci.CatalogType)
.WithMany() .WithMany()
.HasForeignKey(ci => ci.CatalogTypeId); .HasForeignKey(ci => ci.CatalogTypeId);
}
} }
} }

23
src/Infrastructure/Data/Config/CatalogTypeConfiguration.cs

@ -2,21 +2,20 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Entities;
namespace Microsoft.eShopWeb.Infrastructure.Data.Config namespace Microsoft.eShopWeb.Infrastructure.Data.Config;
public class CatalogTypeConfiguration : IEntityTypeConfiguration<CatalogType>
{ {
public class CatalogTypeConfiguration : IEntityTypeConfiguration<CatalogType> public void Configure(EntityTypeBuilder<CatalogType> builder)
{ {
public void Configure(EntityTypeBuilder<CatalogType> builder) builder.HasKey(ci => ci.Id);
{
builder.HasKey(ci => ci.Id);
builder.Property(ci => ci.Id) builder.Property(ci => ci.Id)
.UseHiLo("catalog_type_hilo") .UseHiLo("catalog_type_hilo")
.IsRequired(); .IsRequired();
builder.Property(cb => cb.Type) builder.Property(cb => cb.Type)
.IsRequired() .IsRequired()
.HasMaxLength(100); .HasMaxLength(100);
}
} }
} }

69
src/Infrastructure/Data/Config/OrderConfiguration.cs

@ -2,43 +2,42 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
namespace Microsoft.eShopWeb.Infrastructure.Data.Config namespace Microsoft.eShopWeb.Infrastructure.Data.Config;
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{ {
public class OrderConfiguration : IEntityTypeConfiguration<Order> public void Configure(EntityTypeBuilder<Order> builder)
{ {
public void Configure(EntityTypeBuilder<Order> builder) var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems));
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
builder.Property(b => b.BuyerId)
.IsRequired()
.HasMaxLength(256);
builder.OwnsOne(o => o.ShipToAddress, a =>
{ {
var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); a.WithOwner();
navigation.SetPropertyAccessMode(PropertyAccessMode.Field); a.Property(a => a.ZipCode)
.HasMaxLength(18)
builder.Property(b => b.BuyerId) .IsRequired();
.IsRequired()
.HasMaxLength(256); a.Property(a => a.Street)
.HasMaxLength(180)
builder.OwnsOne(o => o.ShipToAddress, a => .IsRequired();
{
a.WithOwner(); a.Property(a => a.State)
.HasMaxLength(60);
a.Property(a => a.ZipCode)
.HasMaxLength(18) a.Property(a => a.Country)
.IsRequired(); .HasMaxLength(90)
.IsRequired();
a.Property(a => a.Street)
.HasMaxLength(180) a.Property(a => a.City)
.IsRequired(); .HasMaxLength(100)
.IsRequired();
a.Property(a => a.State) });
.HasMaxLength(60);
a.Property(a => a.Country)
.HasMaxLength(90)
.IsRequired();
a.Property(a => a.City)
.HasMaxLength(100)
.IsRequired();
});
}
} }
} }

27
src/Infrastructure/Data/Config/OrderItemConfiguration.cs

@ -2,24 +2,23 @@
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
namespace Microsoft.eShopWeb.Infrastructure.Data.Config namespace Microsoft.eShopWeb.Infrastructure.Data.Config;
public class OrderItemConfiguration : IEntityTypeConfiguration<OrderItem>
{ {
public class OrderItemConfiguration : IEntityTypeConfiguration<OrderItem> public void Configure(EntityTypeBuilder<OrderItem> builder)
{ {
public void Configure(EntityTypeBuilder<OrderItem> builder) builder.OwnsOne(i => i.ItemOrdered, io =>
{ {
builder.OwnsOne(i => i.ItemOrdered, io => io.WithOwner();
{
io.WithOwner();
io.Property(cio => cio.ProductName) io.Property(cio => cio.ProductName)
.HasMaxLength(50) .HasMaxLength(50)
.IsRequired(); .IsRequired();
}); });
builder.Property(oi => oi.UnitPrice) builder.Property(oi => oi.UnitPrice)
.IsRequired(true) .IsRequired(true)
.HasColumnType("decimal(18,2)"); .HasColumnType("decimal(18,2)");
}
} }
} }

11
src/Infrastructure/Data/EfRepository.cs

@ -1,12 +1,11 @@
using Ardalis.Specification.EntityFrameworkCore; using Ardalis.Specification.EntityFrameworkCore;
using Microsoft.eShopWeb.ApplicationCore.Interfaces; using Microsoft.eShopWeb.ApplicationCore.Interfaces;
namespace Microsoft.eShopWeb.Infrastructure.Data namespace Microsoft.eShopWeb.Infrastructure.Data;
public class EfRepository<T> : RepositoryBase<T>, IReadRepository<T>, IRepository<T> where T : class, IAggregateRoot
{ {
public class EfRepository<T> : RepositoryBase<T>, IReadRepository<T>, IRepository<T> where T : class, IAggregateRoot public EfRepository(CatalogContext dbContext) : base(dbContext)
{ {
public EfRepository(CatalogContext dbContext) : base(dbContext)
{
}
} }
} }

21
src/Infrastructure/Data/FileItem.cs

@ -1,12 +1,11 @@
namespace Microsoft.eShopWeb.Infrastructure.Data namespace Microsoft.eShopWeb.Infrastructure.Data;
public class FileItem
{ {
public class FileItem public string FileName { get; set; }
{ public string Url { get; set; }
public string FileName { get; set; } public long Size { get; set; }
public string Url { get; set; } public string Ext { get; set; }
public long Size { get; set; } public string Type { get; set; }
public string Ext { get; set; } public string DataBase64 { get; set; }
public string Type { get; set; } }
public string DataBase64 { get; set; }
}
}

399
src/Infrastructure/Data/Migrations/20201202111507_InitialModel.cs

@ -1,207 +1,206 @@
using System; using System;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations;
public partial class InitialModel : Migration
{ {
public partial class InitialModel : Migration protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateSequence(
name: "catalog_brand_hilo",
incrementBy: 10);
migrationBuilder.CreateSequence(
name: "catalog_hilo",
incrementBy: 10);
migrationBuilder.CreateSequence(
name: "catalog_type_hilo",
incrementBy: 10);
migrationBuilder.CreateTable(
name: "Baskets",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
BuyerId = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Baskets", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CatalogBrands",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false),
Brand = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CatalogBrands", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CatalogTypes",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false),
Type = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CatalogTypes", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Orders",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
BuyerId = table.Column<string>(type: "nvarchar(max)", nullable: true),
OrderDate = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
ShipToAddress_Street = table.Column<string>(type: "nvarchar(180)", maxLength: 180, nullable: true),
ShipToAddress_City = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
ShipToAddress_State = table.Column<string>(type: "nvarchar(60)", maxLength: 60, nullable: true),
ShipToAddress_Country = table.Column<string>(type: "nvarchar(90)", maxLength: 90, nullable: true),
ShipToAddress_ZipCode = table.Column<string>(type: "nvarchar(18)", maxLength: 18, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Orders", x => x.Id);
});
migrationBuilder.CreateTable(
name: "BasketItems",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
UnitPrice = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
Quantity = table.Column<int>(type: "int", nullable: false),
CatalogItemId = table.Column<int>(type: "int", nullable: false),
BasketId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BasketItems", x => x.Id);
table.ForeignKey(
name: "FK_BasketItems_Baskets_BasketId",
column: x => x.BasketId,
principalTable: "Baskets",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Catalog",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
PictureUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
CatalogTypeId = table.Column<int>(type: "int", nullable: false),
CatalogBrandId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Catalog", x => x.Id);
table.ForeignKey(
name: "FK_Catalog_CatalogBrands_CatalogBrandId",
column: x => x.CatalogBrandId,
principalTable: "CatalogBrands",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Catalog_CatalogTypes_CatalogTypeId",
column: x => x.CatalogTypeId,
principalTable: "CatalogTypes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "OrderItems",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ItemOrdered_CatalogItemId = table.Column<int>(type: "int", nullable: true),
ItemOrdered_ProductName = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
ItemOrdered_PictureUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
UnitPrice = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
Units = table.Column<int>(type: "int", nullable: false),
OrderId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OrderItems", x => x.Id);
table.ForeignKey(
name: "FK_OrderItems_Orders_OrderId",
column: x => x.OrderId,
principalTable: "Orders",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_BasketItems_BasketId",
table: "BasketItems",
column: "BasketId");
migrationBuilder.CreateIndex(
name: "IX_Catalog_CatalogBrandId",
table: "Catalog",
column: "CatalogBrandId");
migrationBuilder.CreateIndex(
name: "IX_Catalog_CatalogTypeId",
table: "Catalog",
column: "CatalogTypeId");
migrationBuilder.CreateIndex(
name: "IX_OrderItems_OrderId",
table: "OrderItems",
column: "OrderId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{ {
protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.DropTable(
{ name: "BasketItems");
migrationBuilder.CreateSequence(
name: "catalog_brand_hilo", migrationBuilder.DropTable(
incrementBy: 10); name: "Catalog");
migrationBuilder.CreateSequence( migrationBuilder.DropTable(
name: "catalog_hilo", name: "OrderItems");
incrementBy: 10);
migrationBuilder.DropTable(
migrationBuilder.CreateSequence( name: "Baskets");
name: "catalog_type_hilo",
incrementBy: 10); migrationBuilder.DropTable(
name: "CatalogBrands");
migrationBuilder.CreateTable(
name: "Baskets", migrationBuilder.DropTable(
columns: table => new name: "CatalogTypes");
{
Id = table.Column<int>(type: "int", nullable: false) migrationBuilder.DropTable(
.Annotation("SqlServer:Identity", "1, 1"), name: "Orders");
BuyerId = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: false)
}, migrationBuilder.DropSequence(
constraints: table => name: "catalog_brand_hilo");
{
table.PrimaryKey("PK_Baskets", x => x.Id); migrationBuilder.DropSequence(
}); name: "catalog_hilo");
migrationBuilder.CreateTable( migrationBuilder.DropSequence(
name: "CatalogBrands", name: "catalog_type_hilo");
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false),
Brand = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CatalogBrands", x => x.Id);
});
migrationBuilder.CreateTable(
name: "CatalogTypes",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false),
Type = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CatalogTypes", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Orders",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
BuyerId = table.Column<string>(type: "nvarchar(max)", nullable: true),
OrderDate = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
ShipToAddress_Street = table.Column<string>(type: "nvarchar(180)", maxLength: 180, nullable: true),
ShipToAddress_City = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
ShipToAddress_State = table.Column<string>(type: "nvarchar(60)", maxLength: 60, nullable: true),
ShipToAddress_Country = table.Column<string>(type: "nvarchar(90)", maxLength: 90, nullable: true),
ShipToAddress_ZipCode = table.Column<string>(type: "nvarchar(18)", maxLength: 18, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Orders", x => x.Id);
});
migrationBuilder.CreateTable(
name: "BasketItems",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
UnitPrice = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
Quantity = table.Column<int>(type: "int", nullable: false),
CatalogItemId = table.Column<int>(type: "int", nullable: false),
BasketId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BasketItems", x => x.Id);
table.ForeignKey(
name: "FK_BasketItems_Baskets_BasketId",
column: x => x.BasketId,
principalTable: "Baskets",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Catalog",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Description = table.Column<string>(type: "nvarchar(max)", nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
PictureUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
CatalogTypeId = table.Column<int>(type: "int", nullable: false),
CatalogBrandId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Catalog", x => x.Id);
table.ForeignKey(
name: "FK_Catalog_CatalogBrands_CatalogBrandId",
column: x => x.CatalogBrandId,
principalTable: "CatalogBrands",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Catalog_CatalogTypes_CatalogTypeId",
column: x => x.CatalogTypeId,
principalTable: "CatalogTypes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "OrderItems",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ItemOrdered_CatalogItemId = table.Column<int>(type: "int", nullable: true),
ItemOrdered_ProductName = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
ItemOrdered_PictureUri = table.Column<string>(type: "nvarchar(max)", nullable: true),
UnitPrice = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
Units = table.Column<int>(type: "int", nullable: false),
OrderId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OrderItems", x => x.Id);
table.ForeignKey(
name: "FK_OrderItems_Orders_OrderId",
column: x => x.OrderId,
principalTable: "Orders",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_BasketItems_BasketId",
table: "BasketItems",
column: "BasketId");
migrationBuilder.CreateIndex(
name: "IX_Catalog_CatalogBrandId",
table: "Catalog",
column: "CatalogBrandId");
migrationBuilder.CreateIndex(
name: "IX_Catalog_CatalogTypeId",
table: "Catalog",
column: "CatalogTypeId");
migrationBuilder.CreateIndex(
name: "IX_OrderItems_OrderId",
table: "OrderItems",
column: "OrderId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BasketItems");
migrationBuilder.DropTable(
name: "Catalog");
migrationBuilder.DropTable(
name: "OrderItems");
migrationBuilder.DropTable(
name: "Baskets");
migrationBuilder.DropTable(
name: "CatalogBrands");
migrationBuilder.DropTable(
name: "CatalogTypes");
migrationBuilder.DropTable(
name: "Orders");
migrationBuilder.DropSequence(
name: "catalog_brand_hilo");
migrationBuilder.DropSequence(
name: "catalog_hilo");
migrationBuilder.DropSequence(
name: "catalog_type_hilo");
}
} }
} }

87
src/Infrastructure/Data/Migrations/20211026175614_FixBuyerId.cs

@ -1,53 +1,52 @@
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations namespace Microsoft.eShopWeb.Infrastructure.Data.Migrations;
public partial class FixBuyerId : Migration
{ {
public partial class FixBuyerId : Migration protected override void Up(MigrationBuilder migrationBuilder)
{ {
protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.AlterColumn<string>(
{ name: "BuyerId",
migrationBuilder.AlterColumn<string>( table: "Orders",
name: "BuyerId", type: "nvarchar(256)",
table: "Orders", maxLength: 256,
type: "nvarchar(256)", nullable: false,
maxLength: 256, defaultValue: "",
nullable: false, oldClrType: typeof(string),
defaultValue: "", oldType: "nvarchar(max)",
oldClrType: typeof(string), oldNullable: true);
oldType: "nvarchar(max)",
oldNullable: true);
migrationBuilder.AlterColumn<string>( migrationBuilder.AlterColumn<string>(
name: "BuyerId", name: "BuyerId",
table: "Baskets", table: "Baskets",
type: "nvarchar(256)", type: "nvarchar(256)",
maxLength: 256, maxLength: 256,
nullable: false, nullable: false,
oldClrType: typeof(string), oldClrType: typeof(string),
oldType: "nvarchar(40)", oldType: "nvarchar(40)",
oldMaxLength: 40); oldMaxLength: 40);
} }
protected override void Down(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.AlterColumn<string>( migrationBuilder.AlterColumn<string>(
name: "BuyerId", name: "BuyerId",
table: "Orders", table: "Orders",
type: "nvarchar(max)", type: "nvarchar(max)",
nullable: true, nullable: true,
oldClrType: typeof(string), oldClrType: typeof(string),
oldType: "nvarchar(256)", oldType: "nvarchar(256)",
oldMaxLength: 256); oldMaxLength: 256);
migrationBuilder.AlterColumn<string>( migrationBuilder.AlterColumn<string>(
name: "BuyerId", name: "BuyerId",
table: "Baskets", table: "Baskets",
type: "nvarchar(40)", type: "nvarchar(40)",
maxLength: 40, maxLength: 40,
nullable: false, nullable: false,
oldClrType: typeof(string), oldClrType: typeof(string),
oldType: "nvarchar(256)", oldType: "nvarchar(256)",
oldMaxLength: 256); oldMaxLength: 256);
}
} }
} }

26
src/Infrastructure/Identity/AppIdentityDbContext.cs

@ -2,22 +2,20 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Microsoft.eShopWeb.Infrastructure.Identity namespace Microsoft.eShopWeb.Infrastructure.Identity;
public class AppIdentityDbContext : IdentityDbContext<ApplicationUser>
{ {
public class AppIdentityDbContext : IdentityDbContext<ApplicationUser> public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options)
: base(options)
{ {
public AppIdentityDbContext(DbContextOptions<AppIdentityDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
} }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
} }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save