Browse Source

Merge branch 'dotnet-architecture:dev' into dev

pull/2112/head
KarinKnopper 1 year ago
committed by GitHub
parent
commit
5e60ac1acc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
532 changed files with 36439 additions and 52945 deletions
  1. +132
    -0
      .editorconfig
  2. +1
    -1
      .github/workflows/catalog-api.yml
  3. +1
    -1
      .github/workflows/ordering-api.yml
  4. +2
    -0
      .gitignore
  5. +1
    -1
      CONTRIBUTING.md
  6. +3
    -3
      README.md
  7. +2
    -1
      branch-guide.md
  8. +1
    -1
      deploy/k8s/helm/catalog-api/templates/configmap.yaml
  9. +1
    -1
      deploy/k8s/helm/identity-api/templates/configmap.yaml
  10. +1
    -0
      deploy/k8s/helm/inf.yaml
  11. +1
    -1
      deploy/k8s/helm/ordering-api/templates/configmap.yaml
  12. +1
    -1
      deploy/k8s/helm/ordering-backgroundtasks/templates/configmap.yaml
  13. +1
    -1
      deploy/k8s/helm/webhooks-api/templates/configmap.yaml
  14. +4
    -3
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile
  15. +1
    -2
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop
  16. +62
    -0
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Extensions/Extensions.cs
  17. +0
    -33
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs
  18. +2
    -9
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs
  19. +1
    -1
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs
  20. +0
    -44
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs
  21. +19
    -16
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj
  22. +0
    -1
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Models/UpdateBasketItemsRequest.cs
  23. +24
    -23
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs
  24. +0
    -7
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Properties/launchSettings.json
  25. +0
    -1
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs
  26. +5
    -5
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderingService.cs
  27. +0
    -196
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs
  28. +131
    -8
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json
  29. +6
    -3
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json
  30. +0
    -55
      src/ApiGateways/Mobile.Bff.Shopping/aggregator/azds.yaml
  31. +0
    -11
      src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/HomeController.cs
  32. +4
    -3
      src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile
  33. +1
    -2
      src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop
  34. +63
    -0
      src/ApiGateways/Web.Bff.Shopping/aggregator/Extensions/Extensions.cs
  35. +0
    -34
      src/ApiGateways/Web.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs
  36. +12
    -26
      src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs
  37. +1
    -1
      src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs
  38. +0
    -40
      src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs
  39. +38
    -24
      src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs
  40. +3
    -20
      src/ApiGateways/Web.Bff.Shopping/aggregator/Properties/launchSettings.json
  41. +1
    -1
      src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs
  42. +5
    -5
      src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs
  43. +0
    -199
      src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs
  44. +7
    -22
      src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj
  45. +3
    -10
      src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.Development.json
  46. +131
    -8
      src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json
  47. +0
    -11
      src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json
  48. +0
    -55
      src/ApiGateways/Web.Bff.Shopping/aggregator/azds.yaml
  49. +0
    -11
      src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj
  50. +0
    -22
      src/BuildingBlocks/Devspaces.Support/DevspacesMessageHandler.cs
  51. +0
    -6
      src/BuildingBlocks/Devspaces.Support/GlobalUsings.cs
  52. +0
    -10
      src/BuildingBlocks/Devspaces.Support/HttpClientBuilderDevspacesExtensions.cs
  53. +0
    -10
      src/BuildingBlocks/Devspaces.Support/ServiceCollectionDevspacesExtensions.cs
  54. +6
    -4
      src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj
  55. +1
    -2
      src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs
  56. +0
    -3
      src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs
  57. +2
    -4
      src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs
  58. +2
    -4
      src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs
  59. +0
    -6
      src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs
  60. +1
    -1
      src/BuildingBlocks/EventBus/EventBus/EventBus.csproj
  61. +1
    -1
      src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs
  62. +2
    -2
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs
  63. +11
    -11
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
  64. +5
    -6
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj
  65. +0
    -1
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/GlobalUsings.cs
  66. +1
    -1
      src/BuildingBlocks/EventBus/EventBusServiceBus/DefaultServiceBusPersisterConnection.cs
  67. +10
    -9
      src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs
  68. +4
    -5
      src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj
  69. +0
    -6
      src/BuildingBlocks/EventBus/EventBusServiceBus/GlobalUsings.cs
  70. +5
    -5
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj
  71. +2
    -2
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs
  72. +9
    -9
      src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj
  73. +15
    -15
      src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs
  74. +93
    -0
      src/Directory.Packages.props
  75. +6
    -5
      src/NuGet.config
  76. +0
    -28
      src/Services/Basket/Basket.API/Auth/Client/enable-token-client.js
  77. +0
    -8896
      src/Services/Basket/Basket.API/Auth/Client/oidc-token-manager.js
  78. +0
    -13
      src/Services/Basket/Basket.API/Auth/Client/popup.html
  79. +0
    -25
      src/Services/Basket/Basket.API/Auth/Server/AuthorizationHeaderParameterOperationFilter.cs
  80. +9
    -44
      src/Services/Basket/Basket.API/Basket.API.csproj
  81. +2
    -2
      src/Services/Basket/Basket.API/Controllers/BasketController.cs
  82. +0
    -11
      src/Services/Basket/Basket.API/Controllers/HomeController.cs
  83. +0
    -37
      src/Services/Basket/Basket.API/CustomExtensionMethods.cs
  84. +4
    -3
      src/Services/Basket/Basket.API/Dockerfile
  85. +1
    -1
      src/Services/Basket/Basket.API/Dockerfile.develop
  86. +20
    -0
      src/Services/Basket/Basket.API/Extensions/Extensions.cs
  87. +29
    -61
      src/Services/Basket/Basket.API/GlobalUsings.cs
  88. +0
    -11
      src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs
  89. +0
    -16
      src/Services/Basket/Basket.API/Infrastructure/Exceptions/BasketDomainException.cs
  90. +0
    -18
      src/Services/Basket/Basket.API/Infrastructure/Exceptions/FailingMiddlewareAppBuilderExtensions.cs
  91. +0
    -47
      src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs
  92. +0
    -9
      src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs
  93. +0
    -26
      src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs
  94. +0
    -90
      src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingMiddleware.cs
  95. +0
    -10
      src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingOptions.cs
  96. +0
    -20
      src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingStartupFilter.cs
  97. +0
    -14
      src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs
  98. +2
    -2
      src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs
  99. +3
    -3
      src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs
  100. +19
    -90
      src/Services/Basket/Basket.API/Program.cs

+ 132
- 0
.editorconfig View File

@ -0,0 +1,132 @@
###############################
# 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_prefer_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
###############################
# 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

+ 1
- 1
.github/workflows/catalog-api.yml View File

@ -22,7 +22,7 @@ on:
env: env:
SERVICE: catalog-api SERVICE: catalog-api
IMAGE: catalog.api IMAGE: catalog.api
DOTNET_VERSION: 6.0.x
DOTNET_VERSION: 7.0.x
PROJECT_PATH: Services/Catalog/Catalog.API PROJECT_PATH: Services/Catalog/Catalog.API
TESTS_PATH: Services/Catalog/Catalog.UnitTests TESTS_PATH: Services/Catalog/Catalog.UnitTests


+ 1
- 1
.github/workflows/ordering-api.yml View File

@ -22,7 +22,7 @@ on:
env: env:
SERVICE: ordering-api SERVICE: ordering-api
IMAGE: ordering.api IMAGE: ordering.api
DOTNET_VERSION: 6.0.x
DOTNET_VERSION: 7.0.x
PROJECT_PATH: Services/Ordering/Ordering.API PROJECT_PATH: Services/Ordering/Ordering.API
TESTS_PATH: Services/Ordering/Ordering.UnitTests TESTS_PATH: Services/Ordering/Ordering.UnitTests


+ 2
- 0
.gitignore View File

@ -281,3 +281,5 @@ pub/
src/**/app.yaml src/**/app.yaml
src/**/inf.yaml src/**/inf.yaml
.angular/
/src/Services/Identity/Identity.API/keys/*.json

+ 1
- 1
CONTRIBUTING.md View File

@ -47,7 +47,7 @@ All contributions must be submitted as a [Pull Request (PR)](https://help.github
The main branches are **`dev`** and **`master`**: The main branches are **`dev`** and **`master`**:
- **`dev`**: Contains the latest code **and it is the branch actively developed**. - **`dev`**: Contains the latest code **and it is the branch actively developed**.
**All PRs must be against `dev` branch to be considered**. This branch is developed using `.NET 5`
**All PRs must be against `dev` branch to be considered**. This branch is developed using `.NET 7`
- **`main`**: Synced from time to time from **`dev`**. It contains "stable" code.This branch contains changes specific to `.NET Core 3.1` (**Keep in mind "stable" does not mean PRODUCTION-READY!**) - **`main`**: Synced from time to time from **`dev`**. It contains "stable" code.This branch contains changes specific to `.NET Core 3.1` (**Keep in mind "stable" does not mean PRODUCTION-READY!**)


+ 3
- 3
README.md View File

@ -72,9 +72,9 @@ In the future, more features will be implemented in the advanced scenario.
**NEWS / ANNOUNCEMENTS** **NEWS / ANNOUNCEMENTS**
Do you want to be up-to-date on .NET Architecture guidance and reference apps like eShopOnContainers? --> Subscribe by "WATCHING" this new GitHub repo: https://github.com/dotnet-architecture/News Do you want to be up-to-date on .NET Architecture guidance and reference apps like eShopOnContainers? --> Subscribe by "WATCHING" this new GitHub repo: https://github.com/dotnet-architecture/News
## Updated for .NET 6
## Updated for .NET 7
eShopOnContainers is updated to .NET 6 "wave" of technologies. Not just compilation but also new recommended code in EF Core, ASP.NET Core, and other new related versions with several significant changes.
eShopOnContainers is updated to .NET 7 "wave" of technologies. Not just compilation but also new recommended code in EF Core, ASP.NET Core, and other new related versions with several significant changes.
**See more details in the [Release notes](https://github.com/dotnet-architecture/eShopOnContainers/wiki/Release-notes) wiki page**. **See more details in the [Release notes](https://github.com/dotnet-architecture/eShopOnContainers/wiki/Release-notes) wiki page**.
@ -86,7 +86,7 @@ eShopOnContainers is updated to .NET 6 "wave" of technologies. Not just compilat
### Architecture overview ### Architecture overview
This reference application is cross-platform at the server and client-side, thanks to .NET 6 services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS, or Windows/UWP plus any browser for the client web apps.
This reference application is cross-platform at the server and client-side, thanks to .NET 7 services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS, or Windows/UWP plus any browser for the client web apps.
The architecture proposes a microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using HTTP as the communication protocol between the client apps and the microservices and supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus (a light message broker, to choose between RabbitMQ or Azure Service Bus, underneath) plus other features defined at the [roadmap](https://github.com/dotnet-architecture/eShopOnContainers/wiki/Roadmap). The architecture proposes a microservice oriented architecture implementation with multiple autonomous microservices (each one owning its own data/db) and implementing different approaches within each microservice (simple CRUD vs. DDD/CQRS patterns) using HTTP as the communication protocol between the client apps and the microservices and supports asynchronous communication for data updates propagation across multiple services based on Integration Events and an Event Bus (a light message broker, to choose between RabbitMQ or Azure Service Bus, underneath) plus other features defined at the [roadmap](https://github.com/dotnet-architecture/eShopOnContainers/wiki/Roadmap).
![](img/eshop_logo.png) ![](img/eshop_logo.png)


+ 2
- 1
branch-guide.md View File

@ -2,7 +2,8 @@
Following are the most important branches: Following are the most important branches:
- `dev`: Contains the latest code **and it is the branch actively developed**. Note that **all PRs must be against the `dev` branch to be considered**. This branch is developed using `.NET 6`
- `dev`: Contains the latest code **and it is the branch actively developed**. Note that **all PRs must be against the `dev` branch to be considered**. This branch is developed using `.NET 7`
- `release/net-6`: Contains the code changes specific to the `.NET 6`
- `release/net-5`: Contains the code changes specific to the `.NET 5` - `release/net-5`: Contains the code changes specific to the `.NET 5`
- `release/net-3.1.1`: Contains the code changes specific to the `.NET 3.1` - `release/net-3.1.1`: Contains the code changes specific to the `.NET 3.1`


+ 1
- 1
deploy/k8s/helm/catalog-api/templates/configmap.yaml View File

@ -13,7 +13,7 @@ metadata:
release: {{ .Release.Name }} release: {{ .Release.Name }}
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
data: data:
catalog__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.catalog.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};
catalog__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.catalog.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
catalog__PicBaseUrl: {{ $protocol }}://{{ $webshoppingapigw }}/c/api/v1/catalog/items/[0]/pic/ catalog__PicBaseUrl: {{ $protocol }}://{{ $webshoppingapigw }}/c/api/v1/catalog/items/[0]/pic/
catalog__AzureStorageEnabled: "{{ .Values.inf.misc.useAzureStorage }}" catalog__AzureStorageEnabled: "{{ .Values.inf.misc.useAzureStorage }}"
all__EventBusConnection: {{ .Values.inf.eventbus.constr }} all__EventBusConnection: {{ .Values.inf.eventbus.constr }}


+ 1
- 1
deploy/k8s/helm/identity-api/templates/configmap.yaml View File

@ -20,7 +20,7 @@ metadata:
release: {{ .Release.Name }} release: {{ .Release.Name }}
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
data: data:
identity__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.identity.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};
identity__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.identity.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
identity__keystore: {{ .Values.inf.redis.keystore.constr }} identity__keystore: {{ .Values.inf.redis.keystore.constr }}
all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}" all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}"
mvc_e: http://{{ $mvc_url }} mvc_e: http://{{ $mvc_url }}


+ 1
- 0
deploy/k8s/helm/inf.yaml View File

@ -13,6 +13,7 @@ inf:
user: sa # SQL user user: sa # SQL user
pwd: Pass@word # SQL pwd pwd: Pass@word # SQL pwd
pid: Developer pid: Developer
TrustServerCertificate: true
catalog: # inf.sql.catalog: settings for the catalog-api sql (user, pwd, db) catalog: # inf.sql.catalog: settings for the catalog-api sql (user, pwd, db)
db: CatalogDb # Catalog API SQL db name db: CatalogDb # Catalog API SQL db name
ordering: # inf.sql.ordering: settings for the ordering-api sql (user, pwd, db) ordering: # inf.sql.ordering: settings for the ordering-api sql (user, pwd, db)


+ 1
- 1
deploy/k8s/helm/ordering-api/templates/configmap.yaml View File

@ -11,7 +11,7 @@ metadata:
release: {{ .Release.Name }} release: {{ .Release.Name }}
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
data: data:
ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};
ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
urls__IdentityUrl: http://{{ .Values.app.svc.identity }} urls__IdentityUrl: http://{{ .Values.app.svc.identity }}
all__EventBusConnection: {{ .Values.inf.eventbus.constr }} all__EventBusConnection: {{ .Values.inf.eventbus.constr }}
all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}" all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}"

+ 1
- 1
deploy/k8s/helm/ordering-backgroundtasks/templates/configmap.yaml View File

@ -12,7 +12,7 @@ metadata:
release: {{ .Release.Name }} release: {{ .Release.Name }}
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
data: data:
ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};
ordering__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.ordering.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
ordering__EnableLoadTest: "{{ .Values.inf.misc.useLoadTest }}" ordering__EnableLoadTest: "{{ .Values.inf.misc.useLoadTest }}"
all__EventBusConnection: {{ .Values.inf.eventbus.constr }} all__EventBusConnection: {{ .Values.inf.eventbus.constr }}
all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}" all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}"


+ 1
- 1
deploy/k8s/helm/webhooks-api/templates/configmap.yaml View File

@ -13,7 +13,7 @@ metadata:
release: {{ .Release.Name }} release: {{ .Release.Name }}
heritage: {{ .Release.Service }} heritage: {{ .Release.Service }}
data: data:
webhooks__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.webhooks.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};
webhooks__ConnectionString: Server={{ $sqlsrv }};Initial Catalog={{ .Values.inf.sql.webhooks.db }};User Id={{ .Values.inf.sql.common.user }};Password={{ .Values.inf.sql.common.pwd }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
urls__IdentityUrl: http://{{ $identity }} urls__IdentityUrl: http://{{ $identity }}
urls__IdentityUrlExternal: {{ $protocol }}://{{ $identity }} urls__IdentityUrlExternal: {{ $protocol }}://{{ $identity }}
all__EventBusConnection: {{ .Values.inf.eventbus.constr }} all__EventBusConnection: {{ .Values.inf.eventbus.constr }}


+ 4
- 3
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile View File

@ -1,8 +1,8 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src WORKDIR /src
# It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles
@ -11,7 +11,6 @@ COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWe
COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj"
COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj"
COPY "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj"
COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj" COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj"
COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj"
COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj"
@ -33,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj"
COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj"
COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj"
COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj"
COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj"
COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj"
COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj"
COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj"
@ -42,6 +42,7 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj"
COPY "docker-compose.dcproj" "docker-compose.dcproj" COPY "docker-compose.dcproj" "docker-compose.dcproj"
COPY "Directory.Packages.props" "Directory.Packages.props"
COPY "NuGet.config" "NuGet.config" COPY "NuGet.config" "NuGet.config"
RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln" RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln"


+ 1
- 2
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile.develop View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0
FROM mcr.microsoft.com/dotnet/sdk:7.0
ARG BUILD_CONFIGURATION=Debug ARG BUILD_CONFIGURATION=Debug
ENV ASPNETCORE_ENVIRONMENT=Development ENV ASPNETCORE_ENVIRONMENT=Development
ENV DOTNET_USE_POLLING_FILE_WATCHER=true ENV DOTNET_USE_POLLING_FILE_WATCHER=true
@ -6,7 +6,6 @@ EXPOSE 80
WORKDIR /src WORKDIR /src
COPY ["src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj", "src/ApiGateways/Mobile.Bff.Shopping/aggregator/"] COPY ["src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj", "src/ApiGateways/Mobile.Bff.Shopping/aggregator/"]
COPY ["src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj", "src/BuildingBlocks/Devspaces.Support/"]
COPY ["src/NuGet.config", "src/NuGet.config"] COPY ["src/NuGet.config", "src/NuGet.config"]
RUN dotnet restore src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503 RUN dotnet restore src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503


+ 62
- 0
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Extensions/Extensions.cs View File

@ -0,0 +1,62 @@
internal static class Extensions
{
public static IServiceCollection AddReverseProxy(this IServiceCollection services, IConfiguration configuration)
{
services.AddReverseProxy().LoadFromConfig(configuration.GetRequiredSection("ReverseProxy"));
return services;
}
public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration)
{
services.AddHealthChecks()
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("CatalogUrlHC")), name: "catalogapi-check", tags: new string[] { "catalogapi" })
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("OrderingUrlHC")), name: "orderingapi-check", tags: new string[] { "orderingapi" })
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("BasketUrlHC")), name: "basketapi-check", tags: new string[] { "basketapi" })
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("IdentityUrlHC")), name: "identityapi-check", tags: new string[] { "identityapi" });
return services;
}
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
// Register delegating handlers
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
// Register http services
services.AddHttpClient<IOrderApiClient, OrderApiClient>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
return services;
}
public static IServiceCollection AddGrpcServices(this IServiceCollection services)
{
services.AddTransient<GrpcExceptionInterceptor>();
services.AddScoped<IBasketService, BasketService>();
services.AddGrpcClient<Basket.BasketClient>((services, options) =>
{
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket;
options.Address = new Uri(basketApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
services.AddScoped<ICatalogService, CatalogService>();
services.AddGrpcClient<Catalog.CatalogClient>((services, options) =>
{
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog;
options.Address = new Uri(catalogApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
services.AddScoped<IOrderingService, OrderingService>();
services.AddGrpcClient<GrpcOrdering.OrderingGrpc.OrderingGrpcClient>((services, options) =>
{
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering;
options.Address = new Uri(orderingApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
return services;
}
}

+ 0
- 33
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs View File

@ -1,33 +0,0 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters
{
namespace Basket.API.Infrastructure.Filters
{
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// Check for authorize attribute
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() ||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
if (!hasAuthorize) return;
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
var oAuthScheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
};
operation.Security = new List<OpenApiSecurityRequirement>
{
new()
{
[ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator" }
}
};
}
}
}
}

+ 2
- 9
src/ApiGateways/Mobile.Bff.Shopping/aggregator/GlobalUsings.cs View File

@ -1,34 +1,25 @@
global using CatalogApi; global using CatalogApi;
global using Devspaces.Support;
global using Grpc.Core.Interceptors; global using Grpc.Core.Interceptors;
global using Grpc.Core; global using Grpc.Core;
global using GrpcBasket; global using GrpcBasket;
global using GrpcOrdering;
global using HealthChecks.UI.Client; global using HealthChecks.UI.Client;
global using Microsoft.AspNetCore.Authentication.JwtBearer; global using Microsoft.AspNetCore.Authentication.JwtBearer;
global using Microsoft.AspNetCore.Authentication; global using Microsoft.AspNetCore.Authentication;
global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Authorization;
global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Diagnostics.HealthChecks; global using Microsoft.AspNetCore.Diagnostics.HealthChecks;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.Mvc;
global using Microsoft.AspNetCore;
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters;
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure;
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator;
global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Diagnostics.HealthChecks; global using Microsoft.Extensions.Diagnostics.HealthChecks;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options; global using Microsoft.Extensions.Options;
global using Microsoft.OpenApi.Models; global using Microsoft.OpenApi.Models;
global using Serilog;
global using Swashbuckle.AspNetCore.SwaggerGen;
global using System.Collections.Generic; global using System.Collections.Generic;
global using System.IdentityModel.Tokens.Jwt; global using System.IdentityModel.Tokens.Jwt;
global using System.Linq; global using System.Linq;
@ -39,3 +30,5 @@ global using System.Text.Json;
global using System.Threading.Tasks; global using System.Threading.Tasks;
global using System.Threading; global using System.Threading;
global using System; global using System;
global using Microsoft.IdentityModel.Tokens;
global using Services.Common;

+ 1
- 1
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs View File

@ -28,7 +28,7 @@ public class GrpcExceptionInterceptor : Interceptor
} }
catch (RpcException e) catch (RpcException e)
{ {
_logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message);
_logger.LogError(e, "Error calling via gRPC: {Status}", e.Status);
return default; return default;
} }
} }


+ 0
- 44
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs View File

@ -1,44 +0,0 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure;
public class HttpClientAuthorizationDelegatingHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<HttpClientAuthorizationDelegatingHandler> _logger;
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor, ILogger<HttpClientAuthorizationDelegatingHandler> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Version = new System.Version(2, 0);
request.Method = HttpMethod.Get;
var authorizationHeader = _httpContextAccessor.HttpContext
.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authorizationHeader))
{
request.Headers.Add("Authorization", new List<string>() { authorizationHeader });
}
var token = await GetToken();
if (token != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
async Task<string> GetToken()
{
const string ACCESS_TOKEN = "access_token";
return await _httpContextAccessor.HttpContext
.GetTokenAsync(ACCESS_TOKEN);
}
}

+ 19
- 16
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<AssemblyName>Mobile.Shopping.HttpAggregator</AssemblyName> <AssemblyName>Mobile.Shopping.HttpAggregator</AssemblyName>
<RootNamespace>Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator</RootNamespace> <RootNamespace>Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator</RootNamespace>
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath> <DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
@ -10,27 +10,30 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Folder Include="wwwroot\" />
<Compile Remove="wwwroot\**" />
<Content Remove="wwwroot\**" />
<EmbeddedResource Remove="wwwroot\**" />
<None Remove="wwwroot\**" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="5.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="5.0.1" />
<PackageReference Include="Google.Protobuf" Version="3.15.0" />
<PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" Version="2.34.0" />
<PackageReference Include="Grpc.Core" Version="2.34.0" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.34.0" />
<PackageReference Include="Grpc.Tools" Version="2.34.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.2" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0-dev-00834" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
<PackageReference Include="Yarp.ReverseProxy" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" />
<PackageReference Include="AspNetCore.HealthChecks.Uris" />
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" />
<PackageReference Include="Grpc.Core" />
<PackageReference Include="Grpc.Net.ClientFactory" />
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
<PackageReference Include="Swashbuckle.AspNetCore" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\Devspaces.Support\Devspaces.Support.csproj" />
<ProjectReference Include="..\..\..\Services\Services.Common\Services.Common.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>


+ 0
- 1
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Models/UpdateBasketItemsRequest.cs View File

@ -2,7 +2,6 @@
public class UpdateBasketItemsRequest public class UpdateBasketItemsRequest
{ {
public string BasketId { get; set; } public string BasketId { get; set; }
public ICollection<UpdateBasketItemData> Updates { get; set; } public ICollection<UpdateBasketItemData> Updates { get; set; }


+ 24
- 23
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs View File

@ -1,23 +1,24 @@
await BuildWebHost(args).RunAsync();
IWebHost BuildWebHost(string[] args) =>
WebHost
.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(cb =>
{
var sources = cb.Sources;
sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource()
{
Optional = true,
Path = "appsettings.localhost.json",
ReloadOnChange = false
});
})
.UseStartup<Startup>()
.UseSerilog((builderContext, config) =>
{
config
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console();
})
.Build();
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.Services.AddReverseProxy(builder.Configuration);
builder.Services.AddControllers();
builder.Services.AddHealthChecks(builder.Configuration);
builder.Services.AddApplicationServices();
builder.Services.AddGrpcServices();
builder.Services.Configure<UrlsConfig>(builder.Configuration.GetSection("urls"));
var app = builder.Build();
app.UseServiceDefaults();
app.UseHttpsRedirection();
app.MapControllers();
app.MapReverseProxy();
await app.RunAsync();

+ 0
- 7
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Properties/launchSettings.json View File

@ -24,13 +24,6 @@
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}, },
"applicationUrl": "http://localhost:61632/" "applicationUrl": "http://localhost:61632/"
},
"Azure Dev Spaces": {
"commandName": "AzureDevSpaces",
"launchBrowser": true,
"resourceGroup": "eshoptestedu",
"aksName": "eshoptestedu",
"subscriptionId": "e3035ac1-c06c-4daf-8939-57b3c5f1f759"
} }
} }
} }

+ 0
- 1
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs View File

@ -5,5 +5,4 @@ public interface IBasketService
Task<BasketData> GetByIdAsync(string id); Task<BasketData> GetByIdAsync(string id);
Task UpdateAsync(BasketData currentBasket); Task UpdateAsync(BasketData currentBasket);
} }

+ 5
- 5
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/OrderingService.cs View File

@ -2,10 +2,10 @@
public class OrderingService : IOrderingService public class OrderingService : IOrderingService
{ {
private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
private readonly GrpcOrdering.OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
private readonly ILogger<OrderingService> _logger; private readonly ILogger<OrderingService> _logger;
public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
public OrderingService(GrpcOrdering.OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
{ {
_orderingGrpcClient = orderingGrpcClient; _orderingGrpcClient = orderingGrpcClient;
_logger = logger; _logger = logger;
@ -48,14 +48,14 @@ public class OrderingService : IOrderingService
return data; return data;
} }
private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
private GrpcOrdering.CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
{ {
var command = new CreateOrderDraftCommand
var command = new GrpcOrdering.CreateOrderDraftCommand
{ {
BuyerId = basketData.BuyerId, BuyerId = basketData.BuyerId,
}; };
basketData.Items.ForEach(i => command.Items.Add(new BasketItem
basketData.Items.ForEach(i => command.Items.Add(new GrpcOrdering.BasketItem
{ {
Id = i.Id, Id = i.Id,
OldUnitPrice = (double)i.OldUnitPrice, OldUnitPrice = (double)i.OldUnitPrice,


+ 0
- 196
src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs View File

@ -1,196 +0,0 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy())
.AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" })
.AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" })
.AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" })
.AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" })
.AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" });
services.AddCustomMvc(Configuration)
.AddCustomAuthentication(Configuration)
.AddDevspaces()
.AddHttpServices()
.AddGrpcServices();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
app.UsePathBase(pathBase);
}
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger().UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1");
c.OAuthClientId("mobileshoppingaggswaggerui");
c.OAuthClientSecret(string.Empty);
c.OAuthRealm(string.Empty);
c.OAuthAppName("Purchase BFF Swagger UI");
});
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapControllers();
endpoints.MapHealthChecks("/hc", new HealthCheckOptions()
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
{
Predicate = r => r.Name.Contains("self")
});
});
}
}
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration)
{
services.AddOptions();
services.Configure<UrlsConfig>(configuration.GetSection("urls"));
services.AddControllers()
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Shopping Aggregator for Mobile Clients",
Version = "v1",
Description = "Shopping Aggregator for Mobile Clients"
});
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows()
{
Implicit = new OpenApiOAuthFlow()
{
AuthorizationUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"),
TokenUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"),
Scopes = new Dictionary<string, string>()
{
{ "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" }
}
}
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed((host) => true)
.AllowCredentials());
});
return services;
}
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
var identityUrl = configuration.GetValue<string>("urls:identity");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "mobileshoppingagg";
});
return services;
}
public static IServiceCollection AddHttpServices(this IServiceCollection services)
{
//register delegating handlers
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//register http services
services.AddHttpClient<IOrderApiClient, OrderApiClient>()
.AddDevspacesSupport();
return services;
}
public static IServiceCollection AddGrpcServices(this IServiceCollection services)
{
services.AddTransient<GrpcExceptionInterceptor>();
services.AddScoped<IBasketService, BasketService>();
services.AddGrpcClient<Basket.BasketClient>((services, options) =>
{
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket;
options.Address = new Uri(basketApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
services.AddScoped<ICatalogService, CatalogService>();
services.AddGrpcClient<Catalog.CatalogClient>((services, options) =>
{
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog;
options.Address = new Uri(catalogApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
services.AddScoped<IOrderingService, OrderingService>();
services.AddGrpcClient<OrderingGrpc.OrderingGrpcClient>((services, options) =>
{
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering;
options.Address = new Uri(orderingApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
return services;
}
}

+ 131
- 8
src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json View File

@ -1,15 +1,138 @@
{ {
"Logging": { "Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"System.Net.Http": "Warning"
}
},
"OpenApi": {
"Endpoint": {
"Name": "Purchase BFF V1"
},
"Document": {
"Description": "Shopping Aggregator for Mobile Clients",
"Title": "Shopping Aggregator for Mobile Clients",
"Version": "v1"
},
"Auth": {
"ClientId": "mobileshoppingaggswaggerui",
"AppName": "Mobile shopping BFF Swagger UI"
}
},
"Identity": {
"Url": "http://localhost:5223",
"Audience": "mobileshoppingagg",
"Scopes": {
"webshoppingagg": "Shopping Aggregator for Mobile Clients"
}
},
"ReverseProxy": {
"Routes": {
"c-short": {
"ClusterId": "catalog",
"Match": {
"Path": "c/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/c" }
]
},
"c-long": {
"ClusterId": "catalog",
"Match": {
"Path": "catalog-api/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/catalog-api" }
]
},
"b-short": {
"ClusterId": "basket",
"Match": {
"Path": "b/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/b" }
]
},
"b-long": {
"ClusterId": "basket",
"Match": {
"Path": "basket-api/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/basket-api" }
]
},
"o-short": {
"ClusterId": "orders",
"Match": {
"Path": "o/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/o" }
]
},
"o-long": {
"ClusterId": "orders",
"Match": {
"Path": "ordering-api/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/ordering-api" }
]
},
"h-long": {
"ClusterId": "signalr",
"Match": {
"Path": "hub/notificationhub/{**catch-all}"
}
} }
}, },
"Console": {
"LogLevel": {
"Default": "Warning"
"Clusters": {
"basket": {
"Destinations": {
"destination0": {
"Address": "http://localhost:5221"
}
}
},
"catalog": {
"Destinations": {
"destination0": {
"Address": "http://localhost:5222"
}
}
},
"orders": {
"Destinations": {
"destination0": {
"Address": "http://localhost:5224"
}
}
},
"signalr": {
"Destinations": {
"destination0": {
"Address": "http://localhost:5225"
}
}
} }
} }
}
},
"Urls": {
"Basket": "http://localhost:5221",
"Catalog": "http://localhost:5222",
"Orders": "http://localhost:5224",
"Identity": "http://localhost:5223",
"Signalr": "http://localhost:5225",
"GrpcBasket": "http://localhost:6221",
"GrpcCatalog": "http://localhost:6222",
"GrpcOrdering": "http://localhost:6224"
},
"CatalogUrlHC": "http://localhost:5222/hc",
"OrderingUrlHC": "http://localhost:5224/hc",
"BasketUrlHC": "http://localhost:5221/hc",
"IdentityUrlHC": "http://localhost:5223/hc"
} }

+ 6
- 3
src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json View File

@ -8,16 +8,19 @@
"grpcCatalog": "http://localhost:81", "grpcCatalog": "http://localhost:81",
"grpcOrdering": "http://localhost:5581" "grpcOrdering": "http://localhost:5581"
}, },
"IdentityUrlExternal": "http://localhost:5105",
"IdentityUrl": "http://localhost:5105",
"Identity": {
"ExternalUrl": "http://localhost:5105",
"Url": "http://localhost:5105",
},
"Logging": { "Logging": {
"IncludeScopes": false,
"Debug": { "Debug": {
"IncludeScopes": false,
"LogLevel": { "LogLevel": {
"Default": "Debug" "Default": "Debug"
} }
}, },
"Console": { "Console": {
"IncludeScopes": false,
"LogLevel": { "LogLevel": {
"Default": "Debug" "Default": "Debug"
} }


+ 0
- 55
src/ApiGateways/Mobile.Bff.Shopping/aggregator/azds.yaml View File

@ -1,55 +0,0 @@
kind: helm-release
apiVersion: 1.1
build:
context: ..\..\..\..
dockerfile: Dockerfile
install:
chart: ../../../../k8s/helm/mobileshoppingagg
set:
image:
tag: $(tag)
pullPolicy: Never
ingress:
annotations:
kubernetes.io/ingress.class: traefik-azds
hosts:
# This expands to [space.s.]apigwms.<guid>.<region>.aksapp.io
- $(spacePrefix)eshop$(hostSuffix)
inf:
k8s:
dns: $(spacePrefix)eshop$(hostSuffix)
values:
- values.dev.yaml?
- secrets.dev.yaml?
- app.yaml
- inf.yaml
configurations:
develop:
build:
useGitIgnore: true
dockerfile: Dockerfile.develop
container:
syncTarget: /src
sync:
- '**/Pages/**'
- '**/Views/**'
- '**/wwwroot/**'
- '!**/*.{sln,csproj}'
command:
- dotnet
- run
- --no-restore
- --no-build
- --no-launch-profile
- -c
- ${Configuration:-Debug}
iterate:
processesToKill:
- dotnet
- vsdbg
buildCommands:
- - dotnet
- build
- --no-restore
- -c
- ${Configuration:-Debug}

+ 0
- 11
src/ApiGateways/Web.Bff.Shopping/aggregator/Controllers/HomeController.cs View File

@ -1,11 +0,0 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers;
[Route("")]
public class HomeController : Controller
{
[HttpGet]
public IActionResult Index()
{
return new RedirectResult("~/swagger");
}
}

+ 4
- 3
src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile View File

@ -1,8 +1,8 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src WORKDIR /src
# It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles
@ -11,7 +11,6 @@ COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWe
COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj"
COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj"
COPY "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj"
COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj" COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj"
COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj"
COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj"
@ -33,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj"
COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj"
COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj"
COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj"
COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj"
COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj"
COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj"
COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj"
@ -42,6 +42,7 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj"
COPY "docker-compose.dcproj" "docker-compose.dcproj" COPY "docker-compose.dcproj" "docker-compose.dcproj"
COPY "Directory.Packages.props" "Directory.Packages.props"
COPY "NuGet.config" "NuGet.config" COPY "NuGet.config" "NuGet.config"
RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln" RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln"


+ 1
- 2
src/ApiGateways/Web.Bff.Shopping/aggregator/Dockerfile.develop View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0
FROM mcr.microsoft.com/dotnet/sdk:7.0
ARG BUILD_CONFIGURATION=Debug ARG BUILD_CONFIGURATION=Debug
ENV ASPNETCORE_ENVIRONMENT=Development ENV ASPNETCORE_ENVIRONMENT=Development
ENV DOTNET_USE_POLLING_FILE_WATCHER=true ENV DOTNET_USE_POLLING_FILE_WATCHER=true
@ -6,7 +6,6 @@ EXPOSE 80
WORKDIR /src WORKDIR /src
COPY ["src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj", "src/ApiGateways/Web.Bff.Shopping/aggregator/"] COPY ["src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj", "src/ApiGateways/Web.Bff.Shopping/aggregator/"]
COPY ["src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj", "src/BuildingBlocks/Devspaces.Support/"]
COPY ["src/NuGet.config", "src/NuGet.config"] COPY ["src/NuGet.config", "src/NuGet.config"]
RUN dotnet restore src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503 RUN dotnet restore src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503


+ 63
- 0
src/ApiGateways/Web.Bff.Shopping/aggregator/Extensions/Extensions.cs View File

@ -0,0 +1,63 @@
internal static class Extensions
{
public static IServiceCollection AddReverseProxy(this IServiceCollection services, IConfiguration configuration)
{
services.AddReverseProxy().LoadFromConfig(configuration.GetRequiredSection("ReverseProxy"));
return services;
}
public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration)
{
services.AddHealthChecks()
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("CatalogUrlHC")), name: "catalogapi-check", tags: new string[] { "catalogapi" })
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("OrderingUrlHC")), name: "orderingapi-check", tags: new string[] { "orderingapi" })
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("BasketUrlHC")), name: "basketapi-check", tags: new string[] { "basketapi" })
.AddUrlGroup(_ => new Uri(configuration.GetRequiredValue("IdentityUrlHC")), name: "identityapi-check", tags: new string[] { "identityapi" });
return services;
}
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
// Register delegating handlers
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
// Register http services
services.AddHttpClient<IOrderApiClient, OrderApiClient>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
return services;
}
public static IServiceCollection AddGrpcServices(this IServiceCollection services)
{
services.AddTransient<GrpcExceptionInterceptor>();
services.AddScoped<IBasketService, BasketService>();
services.AddGrpcClient<Basket.BasketClient>((services, options) =>
{
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket;
options.Address = new Uri(basketApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
services.AddScoped<ICatalogService, CatalogService>();
services.AddGrpcClient<Catalog.CatalogClient>((services, options) =>
{
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog;
options.Address = new Uri(catalogApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
services.AddScoped<IOrderingService, OrderingService>();
services.AddGrpcClient<GrpcOrdering.OrderingGrpc.OrderingGrpcClient>((services, options) =>
{
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering;
options.Address = new Uri(orderingApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
return services;
}
}

+ 0
- 34
src/ApiGateways/Web.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs View File

@ -1,34 +0,0 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters
{
namespace Basket.API.Infrastructure.Filters
{
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// Check for authorize attribute
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() ||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
if (!hasAuthorize) return;
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
var oAuthScheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
};
operation.Security = new List<OpenApiSecurityRequirement>
{
new()
{
[ oAuthScheme ] = new[] { "Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator" }
}
};
}
}
}
}

+ 12
- 26
src/ApiGateways/Web.Bff.Shopping/aggregator/GlobalUsings.cs View File

@ -1,41 +1,27 @@
global using CatalogApi;
global using Devspaces.Support;
global using Grpc.Core.Interceptors;
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Net;
global using System.Net.Http;
global using System.Net.Http.Headers;
global using System.Text.Json;
global using System.Threading;
global using System.Threading.Tasks;
global using CatalogApi;
global using Grpc.Core; global using Grpc.Core;
global using Grpc.Core.Interceptors;
global using GrpcBasket; global using GrpcBasket;
global using GrpcOrdering;
global using HealthChecks.UI.Client;
global using Microsoft.AspNetCore.Authentication.JwtBearer;
global using Microsoft.AspNetCore.Authentication; global using Microsoft.AspNetCore.Authentication;
global using Microsoft.AspNetCore.Authorization; global using Microsoft.AspNetCore.Authorization;
global using Microsoft.AspNetCore.Builder; global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Diagnostics.HealthChecks;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.Http; global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.Mvc;
global using Microsoft.AspNetCore;
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters;
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure;
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services; global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator;
global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Diagnostics.HealthChecks;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options; global using Microsoft.Extensions.Options;
global using Microsoft.OpenApi.Models;
global using Serilog;
global using Swashbuckle.AspNetCore.SwaggerGen;
global using System.Collections.Generic;
global using System.IdentityModel.Tokens.Jwt;
global using System.Linq;
global using System.Net.Http.Headers;
global using System.Net.Http;
global using System.Net;
global using System.Text.Json;
global using System.Threading.Tasks;
global using System.Threading;
global using System;
global using Services.Common;

+ 1
- 1
src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/GrpcExceptionInterceptor.cs View File

@ -28,7 +28,7 @@ public class GrpcExceptionInterceptor : Interceptor
} }
catch (RpcException e) catch (RpcException e)
{ {
_logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message);
_logger.LogError(e, "Error calling via gRPC: {Status}", e.Status);
return default; return default;
} }
} }


+ 0
- 40
src/ApiGateways/Web.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs View File

@ -1,40 +0,0 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure;
public class HttpClientAuthorizationDelegatingHandler
: DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var authorizationHeader = _httpContextAccessor.HttpContext
.Request.Headers["Authorization"];
if (!string.IsNullOrWhiteSpace(authorizationHeader))
{
request.Headers.Add("Authorization", new List<string>() { authorizationHeader });
}
var token = await GetTokenAsync();
if (token != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
Task<string> GetTokenAsync()
{
const string ACCESS_TOKEN = "access_token";
return _httpContextAccessor.HttpContext
.GetTokenAsync(ACCESS_TOKEN);
}
}

+ 38
- 24
src/ApiGateways/Web.Bff.Shopping/aggregator/Program.cs View File

@ -1,24 +1,38 @@
await BuildWebHost(args).RunAsync();
IWebHost BuildWebHost(string[] args) =>
WebHost
.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(cb =>
{
var sources = cb.Sources;
sources.Insert(3, new Microsoft.Extensions.Configuration.Json.JsonConfigurationSource()
{
Optional = true,
Path = "appsettings.localhost.json",
ReloadOnChange = false
});
})
.UseStartup<Startup>()
.UseSerilog((builderContext, config) =>
{
config
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console();
})
.Build();
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.Services.AddReverseProxy(builder.Configuration);
builder.Services.AddControllers();
builder.Services.AddHealthChecks(builder.Configuration);
builder.Services.AddCors(options =>
{
// TODO: Read allowed origins from configuration
options.AddPolicy("CorsPolicy",
builder => builder
.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
builder.Services.AddApplicationServices();
builder.Services.AddGrpcServices();
builder.Services.Configure<UrlsConfig>(builder.Configuration.GetSection("urls"));
var app = builder.Build();
app.UseServiceDefaults();
app.UseHttpsRedirection();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapReverseProxy();
await app.RunAsync();

+ 3
- 20
src/ApiGateways/Web.Bff.Shopping/aggregator/Properties/launchSettings.json View File

@ -1,29 +1,12 @@
{ {
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57425/",
"sslPort": 0
}
},
"profiles": { "profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"PurchaseForMvc": {
"Web.Shopping.HttpAggregator": {
"commandName": "Project", "commandName": "Project",
"launchBrowser": true, "launchBrowser": true,
"launchUrl": "api/values",
"applicationUrl": "http://localhost:5229/",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:61632/"
}
} }
} }
} }

+ 1
- 1
src/ApiGateways/Web.Bff.Shopping/aggregator/Services/BasketService.cs View File

@ -10,7 +10,7 @@ public class BasketService : IBasketService
_basketClient = basketClient; _basketClient = basketClient;
_logger = logger; _logger = logger;
} }
public async Task<BasketData> GetByIdAsync(string id) public async Task<BasketData> GetByIdAsync(string id)
{ {
_logger.LogDebug("grpc client created, request = {@id}", id); _logger.LogDebug("grpc client created, request = {@id}", id);


+ 5
- 5
src/ApiGateways/Web.Bff.Shopping/aggregator/Services/OrderingService.cs View File

@ -2,10 +2,10 @@
public class OrderingService : IOrderingService public class OrderingService : IOrderingService
{ {
private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
private readonly GrpcOrdering.OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
private readonly ILogger<OrderingService> _logger; private readonly ILogger<OrderingService> _logger;
public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
public OrderingService(GrpcOrdering.OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
{ {
_orderingGrpcClient = orderingGrpcClient; _orderingGrpcClient = orderingGrpcClient;
_logger = logger; _logger = logger;
@ -48,14 +48,14 @@ public class OrderingService : IOrderingService
return data; return data;
} }
private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
private GrpcOrdering.CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
{ {
var command = new CreateOrderDraftCommand
var command = new GrpcOrdering.CreateOrderDraftCommand
{ {
BuyerId = basketData.BuyerId, BuyerId = basketData.BuyerId,
}; };
basketData.Items.ForEach(i => command.Items.Add(new BasketItem
basketData.Items.ForEach(i => command.Items.Add(new GrpcOrdering.BasketItem
{ {
Id = i.Id, Id = i.Id,
OldUnitPrice = (double)i.OldUnitPrice, OldUnitPrice = (double)i.OldUnitPrice,


+ 0
- 199
src/ApiGateways/Web.Bff.Shopping/aggregator/Startup.cs View File

@ -1,199 +0,0 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy())
.AddUrlGroup(new Uri(Configuration["CatalogUrlHC"]), name: "catalogapi-check", tags: new string[] { "catalogapi" })
.AddUrlGroup(new Uri(Configuration["OrderingUrlHC"]), name: "orderingapi-check", tags: new string[] { "orderingapi" })
.AddUrlGroup(new Uri(Configuration["BasketUrlHC"]), name: "basketapi-check", tags: new string[] { "basketapi" })
.AddUrlGroup(new Uri(Configuration["IdentityUrlHC"]), name: "identityapi-check", tags: new string[] { "identityapi" })
.AddUrlGroup(new Uri(Configuration["PaymentUrlHC"]), name: "paymentapi-check", tags: new string[] { "paymentapi" });
services.AddCustomMvc(Configuration)
.AddCustomAuthentication(Configuration)
.AddDevspaces()
.AddApplicationServices()
.AddGrpcServices();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger<Startup>().LogDebug("Using PATH BASE '{pathBase}'", pathBase);
app.UsePathBase(pathBase);
}
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseSwagger().UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1");
c.OAuthClientId("webshoppingaggswaggerui");
c.OAuthClientSecret(string.Empty);
c.OAuthRealm(string.Empty);
c.OAuthAppName("web shopping bff Swagger UI");
});
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapControllers();
endpoints.MapHealthChecks("/hc", new HealthCheckOptions()
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
endpoints.MapHealthChecks("/liveness", new HealthCheckOptions
{
Predicate = r => r.Name.Contains("self")
});
});
}
}
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
var identityUrl = configuration.GetValue<string>("urls:identity");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "webshoppingagg";
});
return services;
}
public static IServiceCollection AddCustomMvc(this IServiceCollection services, IConfiguration configuration)
{
services.AddOptions();
services.Configure<UrlsConfig>(configuration.GetSection("urls"));
services.AddControllers()
.AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Shopping Aggregator for Web Clients",
Version = "v1",
Description = "Shopping Aggregator for Web Clients"
});
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows()
{
Implicit = new OpenApiOAuthFlow()
{
AuthorizationUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/authorize"),
TokenUrl = new Uri($"{configuration.GetValue<string>("IdentityUrlExternal")}/connect/token"),
Scopes = new Dictionary<string, string>()
{
{ "webshoppingagg", "Shopping Aggregator for Web Clients" }
}
}
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
return services;
}
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
//register delegating handlers
services.AddTransient<HttpClientAuthorizationDelegatingHandler>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//register http services
services.AddHttpClient<IOrderApiClient, OrderApiClient>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
.AddDevspacesSupport();
return services;
}
public static IServiceCollection AddGrpcServices(this IServiceCollection services)
{
services.AddTransient<GrpcExceptionInterceptor>();
services.AddScoped<IBasketService, BasketService>();
services.AddGrpcClient<Basket.BasketClient>((services, options) =>
{
var basketApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcBasket;
options.Address = new Uri(basketApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
services.AddScoped<ICatalogService, CatalogService>();
services.AddGrpcClient<Catalog.CatalogClient>((services, options) =>
{
var catalogApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcCatalog;
options.Address = new Uri(catalogApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
services.AddScoped<IOrderingService, OrderingService>();
services.AddGrpcClient<OrderingGrpc.OrderingGrpcClient>((services, options) =>
{
var orderingApi = services.GetRequiredService<IOptions<UrlsConfig>>().Value.GrpcOrdering;
options.Address = new Uri(orderingApi);
}).AddInterceptor<GrpcExceptionInterceptor>();
return services;
}
}

+ 7
- 22
src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj View File

@ -1,37 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<AssemblyName>Web.Shopping.HttpAggregator</AssemblyName> <AssemblyName>Web.Shopping.HttpAggregator</AssemblyName>
<RootNamespace>Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator</RootNamespace> <RootNamespace>Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator</RootNamespace>
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath> <DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Folder Include="wwwroot\" />
<PackageReference Include="Yarp.ReverseProxy" />
<PackageReference Include="AspNetCore.HealthChecks.Uris" />
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" />
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="5.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="5.0.1" />
<PackageReference Include="Google.Protobuf" Version="3.15.0" />
<PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" Version="2.34.0" />
<PackageReference Include="Grpc.Core" Version="2.34.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.34.0" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.34.0" />
<PackageReference Include="Grpc.Tools" Version="2.34.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.2" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0-dev-00834" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\Devspaces.Support\Devspaces.Support.csproj" />
<ProjectReference Include="..\..\..\Services\Services.Common\Services.Common.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>


+ 3
- 10
src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.Development.json View File

@ -1,15 +1,8 @@
{ {
"Logging": { "Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Debug"
}
},
"Console": {
"LogLevel": {
"Default": "Debug"
}
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
} }
} }
} }

+ 131
- 8
src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.json View File

@ -1,15 +1,138 @@
{ {
"Logging": { "Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"System.Net.Http": "Warning"
}
},
"OpenApi": {
"Endpoint": {
"Name": "Purchase BFF V1"
},
"Document": {
"Description": "Shopping Aggregator for Web Clients",
"Title": "Shopping Aggregator for Web Clients",
"Version": "v1"
},
"Auth": {
"ClientId": "webshoppingaggswaggerui",
"AppName": "Web Shopping BFF Swagger UI"
}
},
"Identity": {
"Url": "http://localhost:5223",
"Audience": "webshoppingagg",
"Scopes": {
"webshoppingagg": "Shopping Aggregator for Web Clients"
}
},
"ReverseProxy": {
"Routes": {
"c-short": {
"ClusterId": "catalog",
"Match": {
"Path": "c/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/c" }
]
},
"c-long": {
"ClusterId": "catalog",
"Match": {
"Path": "catalog-api/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/catalog-api" }
]
},
"b-short": {
"ClusterId": "basket",
"Match": {
"Path": "b/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/b" }
]
},
"b-long": {
"ClusterId": "basket",
"Match": {
"Path": "basket-api/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/basket-api" }
]
},
"o-short": {
"ClusterId": "orders",
"Match": {
"Path": "o/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/o" }
]
},
"o-long": {
"ClusterId": "orders",
"Match": {
"Path": "ordering-api/{**catch-all}"
},
"Transforms": [
{ "PathRemovePrefix": "/ordering-api" }
]
},
"h-long": {
"ClusterId": "signalr",
"Match": {
"Path": "hub/notificationhub/{**catch-all}"
}
} }
}, },
"Console": {
"LogLevel": {
"Default": "Warning"
"Clusters": {
"basket": {
"Destinations": {
"destination0": {
"Address": "http://localhost:5221"
}
}
},
"catalog": {
"Destinations": {
"destination0": {
"Address": "http://localhost:5222"
}
}
},
"orders": {
"Destinations": {
"destination0": {
"Address": "http://localhost:5224"
}
}
},
"signalr": {
"Destinations": {
"destination0": {
"Address": "http://localhost:5225"
}
}
} }
} }
}
},
"Urls": {
"Basket": "http://localhost:5221",
"Catalog": "http://localhost:5222",
"Orders": "http://localhost:5224",
"Identity": "http://localhost:5223",
"Signalr": "http://localhost:5225",
"GrpcBasket": "http://localhost:6221",
"GrpcCatalog": "http://localhost:6222",
"GrpcOrdering": "http://localhost:6224"
},
"CatalogUrlHC": "http://localhost:5222/hc",
"OrderingUrlHC": "http://localhost:5224/hc",
"BasketUrlHC": "http://localhost:5221/hc",
"IdentityUrlHC": "http://localhost:5223/hc"
} }

+ 0
- 11
src/ApiGateways/Web.Bff.Shopping/aggregator/appsettings.localhost.json View File

@ -1,11 +0,0 @@
{
"urls": {
"basket": "http://localhost:55103",
"catalog": "http://localhost:55101",
"orders": "http://localhost:55102",
"identity": "http://localhost:55105",
"grpcBasket": "http://localhost:5580",
"grpcCatalog": "http://localhost:81",
"grpcOrdering": "http://localhost:5581"
}
}

+ 0
- 55
src/ApiGateways/Web.Bff.Shopping/aggregator/azds.yaml View File

@ -1,55 +0,0 @@
kind: helm-release
apiVersion: 1.1
build:
context: ..\..\..\..
dockerfile: Dockerfile
install:
chart: ../../../../k8s/helm/webshoppingagg
set:
image:
tag: $(tag)
pullPolicy: Never
ingress:
annotations:
kubernetes.io/ingress.class: traefik-azds
hosts:
# This expands to [space.s.]apigwms.<guid>.<region>.aksapp.io
- $(spacePrefix)eshop$(hostSuffix)
inf:
k8s:
dns: $(spacePrefix)eshop$(hostSuffix)
values:
- values.dev.yaml?
- secrets.dev.yaml?
- app.yaml
- inf.yaml
configurations:
develop:
build:
useGitIgnore: true
dockerfile: Dockerfile.develop
container:
syncTarget: /src
sync:
- '**/Pages/**'
- '**/Views/**'
- '**/wwwroot/**'
- '!**/*.{sln,csproj}'
command:
- dotnet
- run
- --no-restore
- --no-build
- --no-launch-profile
- -c
- ${Configuration:-Debug}
iterate:
processesToKill:
- dotnet
- vsdbg
buildCommands:
- - dotnet
- build
- --no-restore
- -c
- ${Configuration:-Debug}

+ 0
- 11
src/BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj View File

@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
</ItemGroup>
</Project>

+ 0
- 22
src/BuildingBlocks/Devspaces.Support/DevspacesMessageHandler.cs View File

@ -1,22 +0,0 @@
namespace Devspaces.Support;
public class DevspacesMessageHandler : DelegatingHandler
{
private const string DevspacesHeaderName = "azds-route-as";
private readonly IHttpContextAccessor _httpContextAccessor;
public DevspacesMessageHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var req = _httpContextAccessor.HttpContext.Request;
if (req.Headers.ContainsKey(DevspacesHeaderName))
{
request.Headers.Add(DevspacesHeaderName, req.Headers[DevspacesHeaderName] as IEnumerable<string>);
}
return base.SendAsync(request, cancellationToken);
}
}

+ 0
- 6
src/BuildingBlocks/Devspaces.Support/GlobalUsings.cs View File

@ -1,6 +0,0 @@
global using Microsoft.AspNetCore.Http;
global using Microsoft.Extensions.DependencyInjection;
global using System.Collections.Generic;
global using System.Net.Http;
global using System.Threading.Tasks;
global using System.Threading;

+ 0
- 10
src/BuildingBlocks/Devspaces.Support/HttpClientBuilderDevspacesExtensions.cs View File

@ -1,10 +0,0 @@
namespace Devspaces.Support;
public static class HttpClientBuilderDevspacesExtensions
{
public static IHttpClientBuilder AddDevspacesSupport(this IHttpClientBuilder builder)
{
builder.AddHttpMessageHandler<DevspacesMessageHandler>();
return builder;
}
}

+ 0
- 10
src/BuildingBlocks/Devspaces.Support/ServiceCollectionDevspacesExtensions.cs View File

@ -1,10 +0,0 @@
namespace Devspaces.Support;
public static class ServiceCollectionDevspacesExtensions
{
public static IServiceCollection AddDevspaces(this IServiceCollection services)
{
services.AddTransient<DevspacesMessageHandler>();
return services;
}
}

+ 6
- 4
src/BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj View File

@ -1,16 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<IsPublishable>false</IsPublishable>
<IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>


+ 1
- 2
src/BuildingBlocks/EventBus/EventBus.Tests/InMemory_SubscriptionManager_Tests.cs View File

@ -1,5 +1,4 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using System;
using System.Linq; using System.Linq;
using Xunit; using Xunit;
@ -18,7 +17,7 @@ namespace EventBus.Tests
public void After_One_Event_Subscription_Should_Contain_The_Event() public void After_One_Event_Subscription_Should_Contain_The_Event()
{ {
var manager = new InMemoryEventBusSubscriptionsManager(); var manager = new InMemoryEventBusSubscriptionsManager();
manager.AddSubscription<TestIntegrationEvent,TestIntegrationEventHandler>();
manager.AddSubscription<TestIntegrationEvent, TestIntegrationEventHandler>();
Assert.True(manager.HasSubscriptionsForEvent<TestIntegrationEvent>()); Assert.True(manager.HasSubscriptionsForEvent<TestIntegrationEvent>());
} }


+ 0
- 3
src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEvent.cs View File

@ -1,7 +1,4 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Text;
namespace EventBus.Tests namespace EventBus.Tests
{ {


+ 2
- 4
src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationEventHandler.cs View File

@ -1,7 +1,4 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace EventBus.Tests namespace EventBus.Tests
@ -15,9 +12,10 @@ namespace EventBus.Tests
Handled = false; Handled = false;
} }
public async Task Handle(TestIntegrationEvent @event)
public Task Handle(TestIntegrationEvent @event)
{ {
Handled = true; Handled = true;
return Task.CompletedTask;
} }
} }
} }

+ 2
- 4
src/BuildingBlocks/EventBus/EventBus.Tests/TestIntegrationOtherEventHandler.cs View File

@ -1,7 +1,4 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace EventBus.Tests namespace EventBus.Tests
@ -15,9 +12,10 @@ namespace EventBus.Tests
Handled = false; Handled = false;
} }
public async Task Handle(TestIntegrationEvent @event)
public Task Handle(TestIntegrationEvent @event)
{ {
Handled = true; Handled = true;
return Task.CompletedTask;
} }
} }
} }

+ 0
- 6
src/BuildingBlocks/EventBus/EventBus/Abstractions/IEventBus.cs View File

@ -8,12 +8,6 @@ public interface IEventBus
where T : IntegrationEvent where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>; where TH : IIntegrationEventHandler<T>;
void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void UnsubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void Unsubscribe<T, TH>() void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent; where T : IntegrationEvent;


+ 1
- 1
src/BuildingBlocks/EventBus/EventBus/EventBus.csproj View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.EventBus</RootNamespace> <RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.EventBus</RootNamespace>
</PropertyGroup> </PropertyGroup>


+ 1
- 1
src/BuildingBlocks/EventBus/EventBus/Events/IntegrationEvent.cs View File

@ -1,7 +1,7 @@
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public record IntegrationEvent public record IntegrationEvent
{
{
public IntegrationEvent() public IntegrationEvent()
{ {
Id = Guid.NewGuid(); Id = Guid.NewGuid();


+ 2
- 2
src/BuildingBlocks/EventBus/EventBusRabbitMQ/DefaultRabbitMQPersistentConnection.cs View File

@ -59,7 +59,7 @@ public class DefaultRabbitMQPersistentConnection
.Or<BrokerUnreachableException>() .Or<BrokerUnreachableException>()
.WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{ {
_logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message);
_logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s", $"{time.TotalSeconds:n1}");
} }
); );
@ -81,7 +81,7 @@ public class DefaultRabbitMQPersistentConnection
} }
else else
{ {
_logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened");
_logger.LogCritical("Fatal error: RabbitMQ connections could not be created and opened");
return false; return false;
} }


+ 11
- 11
src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs View File

@ -1,28 +1,28 @@
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.Extensions.DependencyInjection;
public class EventBusRabbitMQ : IEventBus, IDisposable public class EventBusRabbitMQ : IEventBus, IDisposable
{ {
const string BROKER_NAME = "eshop_event_bus"; const string BROKER_NAME = "eshop_event_bus";
const string AUTOFAC_SCOPE_NAME = "eshop_event_bus";
private readonly IRabbitMQPersistentConnection _persistentConnection; private readonly IRabbitMQPersistentConnection _persistentConnection;
private readonly ILogger<EventBusRabbitMQ> _logger; private readonly ILogger<EventBusRabbitMQ> _logger;
private readonly IEventBusSubscriptionsManager _subsManager; private readonly IEventBusSubscriptionsManager _subsManager;
private readonly ILifetimeScope _autofac;
private readonly IServiceProvider _serviceProvider;
private readonly int _retryCount; private readonly int _retryCount;
private IModel _consumerChannel; private IModel _consumerChannel;
private string _queueName; private string _queueName;
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger, public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger,
ILifetimeScope autofac, IEventBusSubscriptionsManager subsManager, string queueName = null, int retryCount = 5)
IServiceProvider serviceProvider, IEventBusSubscriptionsManager subsManager, string queueName = null, int retryCount = 5)
{ {
_persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
_queueName = queueName; _queueName = queueName;
_consumerChannel = CreateConsumerChannel(); _consumerChannel = CreateConsumerChannel();
_autofac = autofac;
_serviceProvider = serviceProvider;
_retryCount = retryCount; _retryCount = retryCount;
_subsManager.OnEventRemoved += SubsManager_OnEventRemoved; _subsManager.OnEventRemoved += SubsManager_OnEventRemoved;
} }
@ -57,7 +57,7 @@ public class EventBusRabbitMQ : IEventBus, IDisposable
.Or<SocketException>() .Or<SocketException>()
.WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{ {
_logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, $"{time.TotalSeconds:n1}", ex.Message);
_logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s", @event.Id, $"{time.TotalSeconds:n1}");
}); });
var eventName = @event.GetType().Name; var eventName = @event.GetType().Name;
@ -79,7 +79,7 @@ public class EventBusRabbitMQ : IEventBus, IDisposable
var properties = channel.CreateBasicProperties(); var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2; // persistent properties.DeliveryMode = 2; // persistent
_logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id);
_logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id);
channel.BasicPublish( channel.BasicPublish(
exchange: BROKER_NAME, exchange: BROKER_NAME,
@ -122,7 +122,7 @@ public class EventBusRabbitMQ : IEventBus, IDisposable
{ {
_persistentConnection.TryConnect(); _persistentConnection.TryConnect();
} }
_consumerChannel.QueueBind(queue: _queueName, _consumerChannel.QueueBind(queue: _queueName,
exchange: BROKER_NAME, exchange: BROKER_NAME,
routingKey: eventName); routingKey: eventName);
@ -193,7 +193,7 @@ public class EventBusRabbitMQ : IEventBus, IDisposable
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarning(ex, "----- ERROR Processing message \"{Message}\"", message);
_logger.LogWarning(ex, "Error Processing message \"{Message}\"", message);
} }
// Even on exception we take the message off the queue. // Even on exception we take the message off the queue.
@ -240,20 +240,20 @@ public class EventBusRabbitMQ : IEventBus, IDisposable
if (_subsManager.HasSubscriptionsForEvent(eventName)) if (_subsManager.HasSubscriptionsForEvent(eventName))
{ {
await using var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME);
await using var scope = _serviceProvider.CreateAsyncScope();
var subscriptions = _subsManager.GetHandlersForEvent(eventName); var subscriptions = _subsManager.GetHandlersForEvent(eventName);
foreach (var subscription in subscriptions) foreach (var subscription in subscriptions)
{ {
if (subscription.IsDynamic) if (subscription.IsDynamic)
{ {
if (scope.ResolveOptional(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue;
if (scope.ServiceProvider.GetService(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue;
using dynamic eventData = JsonDocument.Parse(message); using dynamic eventData = JsonDocument.Parse(message);
await Task.Yield(); await Task.Yield();
await handler.Handle(eventData); await handler.Handle(eventData);
} }
else else
{ {
var handler = scope.ResolveOptional(subscription.HandlerType);
var handler = scope.ServiceProvider.GetService(subscription.HandlerType);
if (handler == null) continue; if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName); var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonSerializer.Deserialize(message, eventType, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); var integrationEvent = JsonSerializer.Deserialize(message, eventType, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });


+ 5
- 6
src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj View File

@ -1,16 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ</RootNamespace> <RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac" Version="6.1.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Polly" Version="7.2.1" />
<PackageReference Include="RabbitMQ.Client" Version="6.2.1" />
<PackageReference Include="Microsoft.CSharp" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Polly" />
<PackageReference Include="RabbitMQ.Client" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>


+ 0
- 1
src/BuildingBlocks/EventBus/EventBusRabbitMQ/GlobalUsings.cs View File

@ -7,7 +7,6 @@ global using RabbitMQ.Client.Exceptions;
global using System; global using System;
global using System.IO; global using System.IO;
global using System.Net.Sockets; global using System.Net.Sockets;
global using Autofac;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;


+ 1
- 1
src/BuildingBlocks/EventBus/EventBusServiceBus/DefaultServiceBusPersisterConnection.cs View File

@ -27,7 +27,7 @@ public class DefaultServiceBusPersisterConnection : IServiceBusPersisterConnecti
} }
} }
public ServiceBusAdministrationClient AdministrationClient =>
public ServiceBusAdministrationClient AdministrationClient =>
_subscriptionClient; _subscriptionClient;
public ServiceBusClient CreateModel() public ServiceBusClient CreateModel()


+ 10
- 9
src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs View File

@ -1,3 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus;
public class EventBusServiceBus : IEventBus, IAsyncDisposable public class EventBusServiceBus : IEventBus, IAsyncDisposable
@ -5,21 +7,20 @@ public class EventBusServiceBus : IEventBus, IAsyncDisposable
private readonly IServiceBusPersisterConnection _serviceBusPersisterConnection; private readonly IServiceBusPersisterConnection _serviceBusPersisterConnection;
private readonly ILogger<EventBusServiceBus> _logger; private readonly ILogger<EventBusServiceBus> _logger;
private readonly IEventBusSubscriptionsManager _subsManager; private readonly IEventBusSubscriptionsManager _subsManager;
private readonly ILifetimeScope _autofac;
private readonly IServiceProvider _serviceProvider;
private readonly string _topicName = "eshop_event_bus"; private readonly string _topicName = "eshop_event_bus";
private readonly string _subscriptionName; private readonly string _subscriptionName;
private readonly ServiceBusSender _sender; private readonly ServiceBusSender _sender;
private readonly ServiceBusProcessor _processor; private readonly ServiceBusProcessor _processor;
private readonly string AUTOFAC_SCOPE_NAME = "eshop_event_bus";
private const string INTEGRATION_EVENT_SUFFIX = "IntegrationEvent"; private const string INTEGRATION_EVENT_SUFFIX = "IntegrationEvent";
public EventBusServiceBus(IServiceBusPersisterConnection serviceBusPersisterConnection, public EventBusServiceBus(IServiceBusPersisterConnection serviceBusPersisterConnection,
ILogger<EventBusServiceBus> logger, IEventBusSubscriptionsManager subsManager, ILifetimeScope autofac, string subscriptionClientName)
ILogger<EventBusServiceBus> logger, IEventBusSubscriptionsManager subsManager, IServiceProvider serviceProvider, string subscriptionClientName)
{ {
_serviceBusPersisterConnection = serviceBusPersisterConnection; _serviceBusPersisterConnection = serviceBusPersisterConnection;
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
_autofac = autofac;
_serviceProvider = serviceProvider;
_subscriptionName = subscriptionClientName; _subscriptionName = subscriptionClientName;
_sender = _serviceBusPersisterConnection.TopicClient.CreateSender(_topicName); _sender = _serviceBusPersisterConnection.TopicClient.CreateSender(_topicName);
ServiceBusProcessorOptions options = new ServiceBusProcessorOptions { MaxConcurrentCalls = 10, AutoCompleteMessages = false }; ServiceBusProcessorOptions options = new ServiceBusProcessorOptions { MaxConcurrentCalls = 10, AutoCompleteMessages = false };
@ -139,7 +140,7 @@ public class EventBusServiceBus : IEventBus, IAsyncDisposable
var ex = args.Exception; var ex = args.Exception;
var context = args.ErrorSource; var context = args.ErrorSource;
_logger.LogError(ex, "ERROR handling message: {ExceptionMessage} - Context: {@ExceptionContext}", ex.Message, context);
_logger.LogError(ex, "Error handling message - Context: {@ExceptionContext}", context);
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -149,20 +150,20 @@ public class EventBusServiceBus : IEventBus, IAsyncDisposable
var processed = false; var processed = false;
if (_subsManager.HasSubscriptionsForEvent(eventName)) if (_subsManager.HasSubscriptionsForEvent(eventName))
{ {
var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME);
await using var scope = _serviceProvider.CreateAsyncScope();
var subscriptions = _subsManager.GetHandlersForEvent(eventName); var subscriptions = _subsManager.GetHandlersForEvent(eventName);
foreach (var subscription in subscriptions) foreach (var subscription in subscriptions)
{ {
if (subscription.IsDynamic) if (subscription.IsDynamic)
{ {
if (scope.ResolveOptional(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue;
if (scope.ServiceProvider.GetService(subscription.HandlerType) is not IDynamicIntegrationEventHandler handler) continue;
using dynamic eventData = JsonDocument.Parse(message); using dynamic eventData = JsonDocument.Parse(message);
await handler.Handle(eventData); await handler.Handle(eventData);
} }
else else
{ {
var handler = scope.ResolveOptional(subscription.HandlerType);
var handler = scope.ServiceProvider.GetService(subscription.HandlerType);
if (handler == null) continue; if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName); var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonSerializer.Deserialize(message, eventType); var integrationEvent = JsonSerializer.Deserialize(message, eventType);
@ -196,4 +197,4 @@ public class EventBusServiceBus : IEventBus, IAsyncDisposable
_subsManager.Clear(); _subsManager.Clear();
await _processor.CloseAsync(); await _processor.CloseAsync();
} }
}
}

+ 4
- 5
src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj View File

@ -1,15 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus</RootNamespace> <RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autofac" Version="6.1.0" />
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.2.1" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Azure.Messaging.ServiceBus" />
<PackageReference Include="Microsoft.CSharp" />
<PackageReference Include="Microsoft.Extensions.Logging" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>


+ 0
- 6
src/BuildingBlocks/EventBus/EventBusServiceBus/GlobalUsings.cs View File

@ -1,19 +1,13 @@
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
global using static Microsoft.eShopOnContainers.BuildingBlocks.EventBus.InMemoryEventBusSubscriptionsManager;
global using System.Collections.Generic;
global using System.Linq;
global using System.Text.Json.Serialization;
global using System.Threading.Tasks; global using System.Threading.Tasks;
global using System; global using System;
global using Autofac;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Logging;
global using System.Text; global using System.Text;
global using System.Text.Json; global using System.Text.Json;
global using Azure.Messaging.ServiceBus; global using Azure.Messaging.ServiceBus;
global using Azure.Messaging.ServiceBus.Administration; global using Azure.Messaging.ServiceBus.Administration;
global using System;


+ 5
- 5
src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj View File

@ -1,18 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF</RootNamespace> <RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>


+ 2
- 2
src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs View File

@ -7,7 +7,7 @@ public class IntegrationEventLogEntry
{ {
EventId = @event.Id; EventId = @event.Id;
CreationTime = @event.CreationDate; CreationTime = @event.CreationDate;
EventTypeName = @event.GetType().FullName;
EventTypeName = @event.GetType().FullName;
Content = JsonSerializer.Serialize(@event, @event.GetType(), new JsonSerializerOptions Content = JsonSerializer.Serialize(@event, @event.GetType(), new JsonSerializerOptions
{ {
WriteIndented = true WriteIndented = true
@ -29,7 +29,7 @@ public class IntegrationEventLogEntry
public string TransactionId { get; private set; } public string TransactionId { get; private set; }
public IntegrationEventLogEntry DeserializeJsonContent(Type type) public IntegrationEventLogEntry DeserializeJsonContent(Type type)
{
{
IntegrationEvent = JsonSerializer.Deserialize(Content, type, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }) as IntegrationEvent; IntegrationEvent = JsonSerializer.Deserialize(Content, type, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }) as IntegrationEvent;
return this; return this;
} }


+ 9
- 9
src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHost.Customization.csproj View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks> <GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
</PropertyGroup> </PropertyGroup>
@ -10,17 +10,17 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.2">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
<PackageReference Include="Microsoft.NETCore.Platforms" Version="5.0.0" />
<PackageReference Include="Polly" Version="7.2.1" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.NETCore.Platforms" />
<PackageReference Include="Polly" />
<PackageReference Include="System.Data.SqlClient" />
</ItemGroup> </ItemGroup>
</Project> </Project>

+ 15
- 15
src/BuildingBlocks/WebHostCustomization/WebHost.Customization/WebHostExtensions.cs View File

@ -1,30 +1,30 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
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 Polly; using Polly;
using System;
using System.Data.SqlClient;
namespace Microsoft.AspNetCore.Hosting namespace Microsoft.AspNetCore.Hosting
{ {
public static class IWebHostExtensions public static class IWebHostExtensions
{ {
public static bool IsInKubernetes(this IWebHost webHost)
public static bool IsInKubernetes(this IServiceProvider services)
{ {
var cfg = webHost.Services.GetService<IConfiguration>();
var cfg = services.GetService<IConfiguration>();
var orchestratorType = cfg.GetValue<string>("OrchestratorType"); var orchestratorType = cfg.GetValue<string>("OrchestratorType");
return orchestratorType?.ToUpper() == "K8S"; return orchestratorType?.ToUpper() == "K8S";
} }
public static IWebHost MigrateDbContext<TContext>(this IWebHost webHost, Action<TContext, IServiceProvider> seeder) where TContext : DbContext
public static IServiceProvider MigrateDbContext<TContext>(this IServiceProvider services, Action<TContext, IServiceProvider> seeder) where TContext : DbContext
{ {
var underK8s = webHost.IsInKubernetes();
var underK8s = services.IsInKubernetes();
using var scope = webHost.Services.CreateScope();
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILogger<TContext>>();
var context = services.GetService<TContext>();
using var scope = services.CreateScope();
var scopeServices = scope.ServiceProvider;
var logger = scopeServices.GetRequiredService<ILogger<TContext>>();
var context = scopeServices.GetService<TContext>();
try try
{ {
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Hosting
if (underK8s) if (underK8s)
{ {
InvokeSeeder(seeder, context, services);
InvokeSeeder(seeder, context, scopeServices);
} }
else else
{ {
@ -43,14 +43,14 @@ namespace Microsoft.AspNetCore.Hosting
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (exception, timeSpan, retry, ctx) => onRetry: (exception, timeSpan, retry, ctx) =>
{ {
logger.LogWarning(exception, "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}", nameof(TContext), exception.GetType().Name, exception.Message, retry, retries);
logger.LogWarning(exception, "[{prefix}] Error migrating database (attempt {retry} of {retries})", nameof(TContext), retry, retries);
}); });
//if the sql server container is not created on run docker compose this //if the sql server container is not created on run docker compose this
//migration can't fail for network related exception. The retry options for DbContext only //migration can't fail for network related exception. The retry options for DbContext only
//apply to transient exceptions //apply to transient exceptions
// Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service) // Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service)
retry.Execute(() => InvokeSeeder(seeder, context, services));
retry.Execute(() => InvokeSeeder(seeder, context, scopeServices));
} }
logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name); logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name);
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Hosting
} }
} }
return webHost;
return services;
} }
private static void InvokeSeeder<TContext>(Action<TContext, IServiceProvider> seeder, TContext context, IServiceProvider services) private static void InvokeSeeder<TContext>(Action<TContext, IServiceProvider> seeder, TContext context, IServiceProvider services)


+ 93
- 0
src/Directory.Packages.props View File

@ -0,0 +1,93 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AspNetCore.HealthChecks.AzureServiceBus" Version="6.1.0" />
<PackageVersion Include="AspNetCore.HealthChecks.AzureStorage" Version="6.1.2" />
<PackageVersion Include="AspNetCore.HealthChecks.Rabbitmq" Version="6.0.2" />
<PackageVersion Include="AspNetCore.HealthChecks.Redis" Version="6.0.4" />
<PackageVersion Include="AspNetCore.HealthChecks.SqlServer" Version="6.0.2" />
<PackageVersion Include="AspNetCore.HealthChecks.UI" Version="6.0.5" />
<PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="6.0.5" />
<PackageVersion Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="6.0.5" />
<PackageVersion Include="AspNetCore.HealthChecks.Uris" Version="6.0.3" />
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" />
<PackageVersion Include="Azure.Identity" Version="1.8.2" />
<PackageVersion Include="Azure.Messaging.ServiceBus" Version="7.12.0" />
<PackageVersion Include="BuildBundlerMinifier" Version="3.2.449" />
<PackageVersion Include="Dapper" Version="2.0.123" />
<PackageVersion Include="Duende.IdentityServer" Version="6.2.3" />
<PackageVersion Include="Duende.IdentityServer.AspNetIdentity" Version="6.2.3" />
<PackageVersion Include="Duende.IdentityServer.EntityFramework" Version="6.2.3" />
<PackageVersion Include="Duende.IdentityServer.EntityFramework.Storage" Version="6.2.3" />
<PackageVersion Include="Duende.IdentityServer.Storage" Version="6.2.3" />
<PackageVersion Include="FluentValidation.AspNetCore" Version="11.2.2" />
<PackageVersion Include="Google.Protobuf" Version="3.22.0" />
<PackageVersion Include="Grpc.AspNetCore.Server" Version="2.51.0" />
<PackageVersion Include="Grpc.AspNetCore" Version="2.51.0" />
<PackageVersion Include="Grpc.AspNetCore.Server.ClientFactory" Version="2.51.0" />
<PackageVersion Include="Grpc.Core" Version="2.46.6" />
<PackageVersion Include="Grpc.Net.Client" Version="2.51.0" />
<PackageVersion Include="Grpc.Net.ClientFactory" Version="2.51.0" />
<PackageVersion Include="Grpc.Tools" Version="2.51.0" PrivateAssets="All" />
<PackageVersion Include="MediatR" Version="12.0.0" />
<PackageVersion Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0-beta1" />
<PackageVersion Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.22.0-beta1" />
<PackageVersion Include="Microsoft.ApplicationInsights.Kubernetes" Version="6.0.0" />
<PackageVersion Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="7.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="7.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="7.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="7.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="7.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="7.0.3" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.4.0" />
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="7.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.3" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.3" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.3" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="7.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.AzureAppServices" Version="7.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageVersion Include="Microsoft.NETCore.Platforms" Version="7.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.4" />
<PackageVersion Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.2" />
<PackageVersion Include="Polly" Version="7.2.3" />
<PackageVersion Include="RabbitMQ.Client" Version="6.4.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
<PackageVersion Include="System.Data.SqlClient" Version="4.8.5" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
<PackageVersion Include="xunit" Version="2.4.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5" />
<PackageVersion Include="Yarp.ReverseProxy" Version="2.0.0" />
</ItemGroup>
</Project>

+ 6
- 5
src/NuGet.config View File

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<config>
<add key="repositoryPath" value="packages" />
</config>
<packageSources> <packageSources>
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources> </packageSources>
</configuration>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
</configuration>

+ 0
- 28
src/Services/Basket/Basket.API/Auth/Client/enable-token-client.js View File

@ -1,28 +0,0 @@
(function ($, swaggerUi) {
$(function () {
var settings = {
authority: 'https://localhost:5105',
client_id: 'js',
popup_redirect_uri: window.location.protocol
+ '//'
+ window.location.host
+ '/tokenclient/popup.html',
response_type: 'id_token token',
scope: 'openid profile basket',
filter_protocol_claims: true
},
manager = new OidcTokenManager(settings),
$inputApiKey = $('#input_apiKey');
$inputApiKey.on('dblclick', function () {
manager.openPopupForTokenAsync()
.then(function () {
$inputApiKey.val(manager.access_token).change();
}, function (error) {
console.error(error);
});
});
});
})(jQuery, window.swaggerUi);

+ 0
- 8896
src/Services/Basket/Basket.API/Auth/Client/oidc-token-manager.js
File diff suppressed because it is too large
View File


+ 0
- 13
src/Services/Basket/Basket.API/Auth/Client/popup.html View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
</head>
<body>
<script type="text/javascript" src="oidc-token-manager.min.js"></script>
<script type="text/javascript">
new OidcTokenManager().processTokenPopup();
</script>
</body>
</html>

+ 0
- 25
src/Services/Basket/Basket.API/Auth/Server/AuthorizationHeaderParameterOperationFilter.cs View File

@ -1,25 +0,0 @@
namespace Microsoft.eShopOnContainers.Services.Basket.API.Auth.Server;
public class AuthorizationHeaderParameterOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors;
var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);
if (isAuthorized && !allowAnonymous)
{
operation.Parameters ??= new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "Authorization",
In = ParameterLocation.Header,
Description = "access token",
Required = true
});
}
}
}

+ 9
- 44
src/Services/Basket/Basket.API/Basket.API.csproj View File

@ -1,60 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<TargetFramework>net7.0</TargetFramework>
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath> <DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
<UserSecretsId>2964ec8e-0d48-4541-b305-94cab537f867</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Update="web.config">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<PackageReference Include="Grpc.AspNetCore" />
<PackageReference Include="AspNetCore.HealthChecks.Redis" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.12.2" />
<PackageReference Include="Azure.Identity" Version="1.5.0-beta.3" />
<PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="5.1.1" />
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="5.0.1" />
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="5.0.2" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="5.0.1" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.2.0-preview.1" />
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.1" />
<PackageReference Include="Azure.Identity" Version="1.4.0" />
<PackageReference Include="Google.Protobuf" Version="3.15.0" />
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.34.0" />
<PackageReference Include="Grpc.Tools" Version="2.34.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.18.0" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.18.0" />
<PackageReference Include="Microsoft.ApplicationInsights.Kubernetes" Version="2.0.2-beta2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="3.1.18" />
<PackageReference Include="Microsoft.Extensions.Logging.AzureAppServices" Version="6.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.11.1" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.1-dev-00229" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.1-dev-00787" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0-dev-00291" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1-dev-00876" />
<PackageReference Include="Serilog.Sinks.Http" Version="8.0.0-beta.9" />
<PackageReference Include="Serilog.Sinks.Seq" Version="4.1.0-dev-00166" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.1" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.2.1" />
<ItemGroup>
<Protobuf Include="Proto\basket.proto" GrpcServices="Server" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Protobuf Include="Proto\basket.proto" GrpcServices="Server" Generator="MSBuild:Compile" />
<Content Include="@(Protobuf)" />
<None Remove="@(Protobuf)" />
<ProjectReference Include="..\..\Services.Common\Services.Common.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
<InternalsVisibleTo Include="Basket.FunctionalTests" />
</ItemGroup> </ItemGroup>
</Project> </Project>

+ 2
- 2
src/Services/Basket/Basket.API/Controllers/BasketController.cs View File

@ -56,7 +56,7 @@ public class BasketController : ControllerBase
return BadRequest(); return BadRequest();
} }
var userName = this.HttpContext.User.FindFirst(x => x.Type == ClaimTypes.Name).Value;
var userName = User.FindFirst(x => x.Type == ClaimTypes.Name).Value;
var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, userName, basketCheckout.City, basketCheckout.Street, var eventMessage = new UserCheckoutAcceptedIntegrationEvent(userId, userName, basketCheckout.City, basketCheckout.Street,
basketCheckout.State, basketCheckout.Country, basketCheckout.ZipCode, basketCheckout.CardNumber, basketCheckout.CardHolderName, basketCheckout.State, basketCheckout.Country, basketCheckout.ZipCode, basketCheckout.CardNumber, basketCheckout.CardHolderName,
@ -71,7 +71,7 @@ public class BasketController : ControllerBase
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "ERROR Publishing integration event: {IntegrationEventId} from {AppName}", eventMessage.Id, Program.AppName);
_logger.LogError(ex, "Error Publishing integration event: {IntegrationEventId}", eventMessage.Id);
throw; throw;
} }


+ 0
- 11
src/Services/Basket/Basket.API/Controllers/HomeController.cs View File

@ -1,11 +0,0 @@
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers;
public class HomeController : Controller
{
// GET: /<controller>/
public IActionResult Index()
{
return new RedirectResult("~/swagger");
}
}

+ 0
- 37
src/Services/Basket/Basket.API/CustomExtensionMethods.cs View File

@ -1,37 +0,0 @@
namespace Microsoft.eShopOnContainers.Services.Basket.API;
public static class CustomExtensionMethods
{
public static IServiceCollection AddCustomHealthCheck(this IServiceCollection services, IConfiguration configuration)
{
var hcBuilder = services.AddHealthChecks();
hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy());
hcBuilder
.AddRedis(
configuration["ConnectionString"],
name: "redis-check",
tags: new string[] { "redis" });
if (configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
hcBuilder
.AddAzureServiceBusTopic(
configuration["EventBusConnection"],
topicName: "eshop_event_bus",
name: "basket-servicebus-check",
tags: new string[] { "servicebus" });
}
else
{
hcBuilder
.AddRabbitMQ(
$"amqp://{configuration["EventBusConnection"]}",
name: "basket-rabbitmqbus-check",
tags: new string[] { "rabbitmqbus" });
}
return services;
}
}

+ 4
- 3
src/Services/Basket/Basket.API/Dockerfile View File

@ -1,8 +1,8 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src WORKDIR /src
# It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles # It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles
@ -11,7 +11,6 @@ COPY "eShopOnContainers-ServicesAndWebApps.sln" "eShopOnContainers-ServicesAndWe
COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" COPY "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj" "ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj"
COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" COPY "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj" "ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj"
COPY "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj" "BuildingBlocks/Devspaces.Support/Devspaces.Support.csproj"
COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj" COPY "BuildingBlocks/EventBus/EventBus/EventBus.csproj" "BuildingBlocks/EventBus/EventBus/EventBus.csproj"
COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" COPY "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj"
COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj"
@ -33,6 +32,7 @@ COPY "Services/Ordering/Ordering.Infrastructure/Ordering.Infrastructure.csproj"
COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" COPY "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj" "Services/Ordering/Ordering.SignalrHub/Ordering.SignalrHub.csproj"
COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" COPY "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj" "Services/Ordering/Ordering.UnitTests/Ordering.UnitTests.csproj"
COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj" COPY "Services/Payment/Payment.API/Payment.API.csproj" "Services/Payment/Payment.API/Payment.API.csproj"
COPY "Services/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj"
COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" COPY "Services/Webhooks/Webhooks.API/Webhooks.API.csproj" "Services/Webhooks/Webhooks.API/Webhooks.API.csproj"
COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" COPY "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj" "Tests/Services/Application.FunctionalTests/Application.FunctionalTests.csproj"
COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj" COPY "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj"
@ -42,6 +42,7 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj"
COPY "docker-compose.dcproj" "docker-compose.dcproj" COPY "docker-compose.dcproj" "docker-compose.dcproj"
COPY "Directory.Packages.props" "Directory.Packages.props"
COPY "NuGet.config" "NuGet.config" COPY "NuGet.config" "NuGet.config"
RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln" RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln"


+ 1
- 1
src/Services/Basket/Basket.API/Dockerfile.develop View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0
FROM mcr.microsoft.com/dotnet/sdk:7.0
ARG BUILD_CONFIGURATION=Debug ARG BUILD_CONFIGURATION=Debug
ENV ASPNETCORE_ENVIRONMENT=Development ENV ASPNETCORE_ENVIRONMENT=Development
ENV DOTNET_USE_POLLING_FILE_WATCHER=true ENV DOTNET_USE_POLLING_FILE_WATCHER=true


+ 20
- 0
src/Services/Basket/Basket.API/Extensions/Extensions.cs View File

@ -0,0 +1,20 @@
public static class Extensions
{
public static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration)
{
services.AddHealthChecks()
.AddRedis(_ => configuration.GetRequiredConnectionString("redis"), "redis", tags: new[] { "ready", "liveness" });
return services;
}
public static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration configuration)
{
return services.AddSingleton(sp =>
{
var redisConfig = ConfigurationOptions.Parse(configuration.GetRequiredConnectionString("redis"), true);
return ConnectionMultiplexer.Connect(redisConfig);
});
}
}

+ 29
- 61
src/Services/Basket/Basket.API/GlobalUsings.cs View File

@ -1,61 +1,29 @@
global using Autofac.Extensions.DependencyInjection;
global using Autofac;
global using Azure.Core;
global using Azure.Identity;
global using Basket.API.Infrastructure.ActionResults;
global using Basket.API.Infrastructure.Exceptions;
global using Basket.API.Infrastructure.Filters;
global using Basket.API.Infrastructure.Middlewares;
global using Basket.API.IntegrationEvents.EventHandling;
global using Basket.API.IntegrationEvents.Events;
global using Basket.API.Model;
global using Grpc.Core;
global using GrpcBasket;
global using HealthChecks.UI.Client;
global using Microsoft.AspNetCore.Authentication.JwtBearer;
global using Microsoft.AspNetCore.Authorization;
global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Diagnostics.HealthChecks;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.Http.Features;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Mvc.Authorization;
global using Microsoft.AspNetCore.Mvc.Filters;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.AspNetCore.Server.Kestrel.Core;
global using Microsoft.AspNetCore;
global using Azure.Messaging.ServiceBus;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus;
global using Microsoft.eShopOnContainers.Services.Basket.API.Controllers;
global using Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Repositories;
global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling;
global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events;
global using Microsoft.eShopOnContainers.Services.Basket.API.Model;
global using Microsoft.eShopOnContainers.Services.Basket.API.Services;
global using Microsoft.eShopOnContainers.Services.Basket.API;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Diagnostics.HealthChecks;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options;
global using Microsoft.OpenApi.Models;
global using RabbitMQ.Client;
global using Serilog.Context;
global using Serilog;
global using StackExchange.Redis;
global using Swashbuckle.AspNetCore.SwaggerGen;
global using System.Collections.Generic;
global using System.ComponentModel.DataAnnotations;
global using System.IdentityModel.Tokens.Jwt;
global using System.IO;
global using System.Linq;
global using System.Net;
global using System.Security.Claims;
global using System.Text.Json;
global using System.Threading.Tasks;
global using System;
global using System;
global using System.Collections.Generic;
global using System.ComponentModel.DataAnnotations;
global using System.Linq;
global using System.Net;
global using System.Security.Claims;
global using System.Text.Json;
global using System.Threading.Tasks;
global using Basket.API.IntegrationEvents.EventHandling;
global using Basket.API.IntegrationEvents.Events;
global using Basket.API.Model;
global using Basket.API.Repositories;
global using Grpc.Core;
global using GrpcBasket;
global using Microsoft.AspNetCore.Authorization;
global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
global using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.EventHandling;
global using Microsoft.eShopOnContainers.Services.Basket.API.IntegrationEvents.Events;
global using Microsoft.eShopOnContainers.Services.Basket.API.Model;
global using Microsoft.eShopOnContainers.Services.Basket.API.Services;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Logging;
global using Services.Common;
global using StackExchange.Redis;

+ 0
- 11
src/Services/Basket/Basket.API/Infrastructure/ActionResults/InternalServerErrorObjectResult.cs View File

@ -1,11 +0,0 @@
namespace Basket.API.Infrastructure.ActionResults;
public class InternalServerErrorObjectResult : ObjectResult
{
public InternalServerErrorObjectResult(object error)
: base(error)
{
StatusCode = StatusCodes.Status500InternalServerError;
}
}

+ 0
- 16
src/Services/Basket/Basket.API/Infrastructure/Exceptions/BasketDomainException.cs View File

@ -1,16 +0,0 @@
namespace Basket.API.Infrastructure.Exceptions;
public class BasketDomainException : Exception
{
public BasketDomainException()
{ }
public BasketDomainException(string message)
: base(message)
{ }
public BasketDomainException(string message, Exception innerException)
: base(message, innerException)
{ }
}

+ 0
- 18
src/Services/Basket/Basket.API/Infrastructure/Exceptions/FailingMiddlewareAppBuilderExtensions.cs View File

@ -1,18 +0,0 @@
namespace Basket.API.Infrastructure.Middlewares;
public static class FailingMiddlewareAppBuilderExtensions
{
public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder)
{
return UseFailingMiddleware(builder, null);
}
public static IApplicationBuilder UseFailingMiddleware(this IApplicationBuilder builder, Action<FailingOptions> action)
{
var options = new FailingOptions();
action?.Invoke(options);
builder.UseMiddleware<FailingMiddleware>(options);
return builder;
}
}

+ 0
- 47
src/Services/Basket/Basket.API/Infrastructure/Filters/HttpGlobalExceptionFilter.cs View File

@ -1,47 +0,0 @@
namespace Basket.API.Infrastructure.Filters;
public partial class HttpGlobalExceptionFilter : IExceptionFilter
{
private readonly IWebHostEnvironment env;
private readonly ILogger<HttpGlobalExceptionFilter> logger;
public HttpGlobalExceptionFilter(IWebHostEnvironment env, ILogger<HttpGlobalExceptionFilter> logger)
{
this.env = env;
this.logger = logger;
}
public void OnException(ExceptionContext context)
{
logger.LogError(new EventId(context.Exception.HResult),
context.Exception,
context.Exception.Message);
if (context.Exception.GetType() == typeof(BasketDomainException))
{
var json = new JsonErrorResponse
{
Messages = new[] { context.Exception.Message }
};
context.Result = new BadRequestObjectResult(json);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
else
{
var json = new JsonErrorResponse
{
Messages = new[] { "An error occurred. Try it again." }
};
if (env.IsDevelopment())
{
json.DeveloperMessage = context.Exception;
}
context.Result = new InternalServerErrorObjectResult(json);
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
context.ExceptionHandled = true;
}
}

+ 0
- 9
src/Services/Basket/Basket.API/Infrastructure/Filters/JsonErrorResponse.cs View File

@ -1,9 +0,0 @@
namespace Basket.API.Infrastructure.Filters;
public class JsonErrorResponse
{
public string[] Messages { get; set; }
public object DeveloperMessage { get; set; }
}

+ 0
- 26
src/Services/Basket/Basket.API/Infrastructure/Filters/ValidateModelStateFilter.cs View File

@ -1,26 +0,0 @@
namespace Basket.API.Infrastructure.Filters;
public class ValidateModelStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid)
{
return;
}
var validationErrors = context.ModelState
.Keys
.SelectMany(k => context.ModelState[k].Errors)
.Select(e => e.ErrorMessage)
.ToArray();
var json = new JsonErrorResponse
{
Messages = validationErrors
};
context.Result = new BadRequestObjectResult(json);
}
}

+ 0
- 90
src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingMiddleware.cs View File

@ -1,90 +0,0 @@
namespace Basket.API.Infrastructure.Middlewares;
using Microsoft.Extensions.Logging;
public class FailingMiddleware
{
private readonly RequestDelegate _next;
private bool _mustFail;
private readonly FailingOptions _options;
private readonly ILogger _logger;
public FailingMiddleware(RequestDelegate next, ILogger<FailingMiddleware> logger, FailingOptions options)
{
_next = next;
_options = options;
_mustFail = false;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
var path = context.Request.Path;
if (path.Equals(_options.ConfigPath, StringComparison.OrdinalIgnoreCase))
{
await ProcessConfigRequest(context);
return;
}
if (MustFail(context))
{
_logger.LogInformation("Response for path {Path} will fail.", path);
context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Failed due to FailingMiddleware enabled.");
}
else
{
await _next.Invoke(context);
}
}
private async Task ProcessConfigRequest(HttpContext context)
{
var enable = context.Request.Query.Keys.Any(k => k == "enable");
var disable = context.Request.Query.Keys.Any(k => k == "disable");
if (enable && disable)
{
throw new ArgumentException("Must use enable or disable querystring values, but not both");
}
if (disable)
{
_mustFail = false;
await SendOkResponse(context, "FailingMiddleware disabled. Further requests will be processed.");
return;
}
if (enable)
{
_mustFail = true;
await SendOkResponse(context, "FailingMiddleware enabled. Further requests will return HTTP 500");
return;
}
// If reach here, that means that no valid parameter has been passed. Just output status
await SendOkResponse(context, string.Format("FailingMiddleware is {0}", _mustFail ? "enabled" : "disabled"));
return;
}
private async Task SendOkResponse(HttpContext context, string message)
{
context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(message);
}
private bool MustFail(HttpContext context)
{
var rpath = context.Request.Path.Value;
if (_options.NotFilteredPaths.Any(p => p.Equals(rpath, StringComparison.InvariantCultureIgnoreCase)))
{
return false;
}
return _mustFail &&
(_options.EndpointPaths.Any(x => x == rpath)
|| _options.EndpointPaths.Count == 0);
}
}

+ 0
- 10
src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingOptions.cs View File

@ -1,10 +0,0 @@
namespace Basket.API.Infrastructure.Middlewares;
public class FailingOptions
{
public string ConfigPath = "/Failing";
public List<string> EndpointPaths { get; set; } = new List<string>();
public List<string> NotFilteredPaths { get; set; } = new List<string>();
}

+ 0
- 20
src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingStartupFilter.cs View File

@ -1,20 +0,0 @@
namespace Basket.API.Infrastructure.Middlewares;
public class FailingStartupFilter : IStartupFilter
{
private readonly Action<FailingOptions> _options;
public FailingStartupFilter(Action<FailingOptions> optionsAction)
{
_options = optionsAction;
}
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
app.UseFailingMiddleware(_options);
next(app);
};
}
}

+ 0
- 14
src/Services/Basket/Basket.API/Infrastructure/Middlewares/FailingWebHostBuilderExtensions.cs View File

@ -1,14 +0,0 @@
namespace Basket.API.Infrastructure.Middlewares;
public static class WebHostBuildertExtensions
{
public static IWebHostBuilder UseFailing(this IWebHostBuilder builder, Action<FailingOptions> options)
{
builder.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter>(new FailingStartupFilter(options));
});
return builder;
}
}

+ 2
- 2
src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/OrderStartedIntegrationEventHandler.cs View File

@ -15,9 +15,9 @@ public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler<Orde
public async Task Handle(OrderStartedIntegrationEvent @event) public async Task Handle(OrderStartedIntegrationEvent @event)
{ {
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
using (_logger.BeginScope(new List<KeyValuePair<string, object>> { new ("IntegrationEventContext", @event.Id) }))
{ {
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
_logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event);
await _repository.DeleteBasketAsync(@event.UserId.ToString()); await _repository.DeleteBasketAsync(@event.UserId.ToString());
} }


+ 3
- 3
src/Services/Basket/Basket.API/IntegrationEvents/EventHandling/ProductPriceChangedIntegrationEventHandler.cs View File

@ -15,9 +15,9 @@ public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandl
public async Task Handle(ProductPriceChangedIntegrationEvent @event) public async Task Handle(ProductPriceChangedIntegrationEvent @event)
{ {
using (LogContext.PushProperty("IntegrationEventContext", $"{@event.Id}-{Program.AppName}"))
using (_logger.BeginScope(new List<KeyValuePair<string, object>> { new ("IntegrationEventContext", @event.Id) }))
{ {
_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, Program.AppName, @event);
_logger.LogInformation("Handling integration event: {IntegrationEventId} - ({@IntegrationEvent})", @event.Id, @event);
var userIds = _repository.GetUsers(); var userIds = _repository.GetUsers();
@ -36,7 +36,7 @@ public class ProductPriceChangedIntegrationEventHandler : IIntegrationEventHandl
if (itemsToUpdate != null) if (itemsToUpdate != null)
{ {
_logger.LogInformation("----- ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate);
_logger.LogInformation("ProductPriceChangedIntegrationEventHandler - Updating items in basket for user: {BuyerId} ({@Items})", basket.BuyerId, itemsToUpdate);
foreach (var item in itemsToUpdate) foreach (var item in itemsToUpdate)
{ {


+ 19
- 90
src/Services/Basket/Basket.API/Program.cs View File

@ -1,101 +1,30 @@
var configuration = GetConfiguration();
var builder = WebApplication.CreateBuilder(args);
Log.Logger = CreateSerilogLogger(configuration);
builder.AddServiceDefaults();
try
{
Log.Information("Configuring web host ({ApplicationContext})...", Program.AppName);
var host = BuildWebHost(configuration, args);
builder.Services.AddGrpc();
builder.Services.AddControllers();
builder.Services.AddProblemDetails();
Log.Information("Starting web host ({ApplicationContext})...", Program.AppName);
host.Run();
builder.Services.AddHealthChecks(builder.Configuration);
builder.Services.AddRedis(builder.Configuration);
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Program terminated unexpectedly ({ApplicationContext})!", Program.AppName);
return 1;
}
finally
{
Log.CloseAndFlush();
}
builder.Services.AddTransient<ProductPriceChangedIntegrationEventHandler>();
builder.Services.AddTransient<OrderStartedIntegrationEventHandler>();
IWebHost BuildWebHost(IConfiguration configuration, string[] args) =>
WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(false)
.ConfigureKestrel(options =>
{
var ports = GetDefinedPorts(configuration);
options.Listen(IPAddress.Any, ports.httpPort, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
});
builder.Services.AddTransient<IBasketRepository, RedisBasketRepository>();
builder.Services.AddTransient<IIdentityService, IdentityService>();
options.Listen(IPAddress.Any, ports.grpcPort, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
var app = builder.Build();
})
.ConfigureAppConfiguration(x => x.AddConfiguration(configuration))
.UseFailing(options =>
{
options.ConfigPath = "/Failing";
options.NotFilteredPaths.AddRange(new[] { "/hc", "/liveness" });
})
.UseStartup<Startup>()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseSerilog()
.Build();
app.UseServiceDefaults();
Serilog.ILogger CreateSerilogLogger(IConfiguration configuration)
{
var seqServerUrl = configuration["Serilog:SeqServerUrl"];
var logstashUrl = configuration["Serilog:LogstashgUrl"];
return new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithProperty("ApplicationContext", Program.AppName)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Seq(string.IsNullOrWhiteSpace(seqServerUrl) ? "http://seq" : seqServerUrl)
.WriteTo.Http(string.IsNullOrWhiteSpace(logstashUrl) ? "http://logstash:8080" : logstashUrl)
.ReadFrom.Configuration(configuration)
.CreateLogger();
}
app.MapGrpcService<BasketService>();
app.MapControllers();
IConfiguration GetConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
var eventBus = app.Services.GetRequiredService<IEventBus>();
var config = builder.Build();
eventBus.Subscribe<ProductPriceChangedIntegrationEvent, ProductPriceChangedIntegrationEventHandler>();
eventBus.Subscribe<OrderStartedIntegrationEvent, OrderStartedIntegrationEventHandler>();
if (config.GetValue<bool>("UseVault", false))
{
TokenCredential credential = new ClientSecretCredential(
config["Vault:TenantId"],
config["Vault:ClientId"],
config["Vault:ClientSecret"]);
builder.AddAzureKeyVault(new Uri($"https://{config["Vault:Name"]}.vault.azure.net/"), credential);
}
return builder.Build();
}
(int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config)
{
var grpcPort = config.GetValue("GRPC_PORT", 5001);
var port = config.GetValue("PORT", 80);
return (port, grpcPort);
}
public partial class Program
{
public static string Namespace = typeof(Startup).Namespace;
public static string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1);
}
await app.RunAsync();

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

Loading…
Cancel
Save