Compare commits

..

No commits in common. "dev" and "release/net-5" have entirely different histories.

806 changed files with 61342 additions and 62340 deletions

View File

@ -1,132 +0,0 @@
###############################
# 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

View File

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

View File

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

2
.gitignore vendored
View File

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

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`**:
- **`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 7`
**All PRs must be against `dev` branch to be considered**. This branch is developed using `.NET 5`
- **`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!**)

View File

@ -72,9 +72,9 @@ In the future, more features will be implemented in the advanced scenario.
**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
## Updated for .NET 7
## Updated for .NET 5
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.
eShopOnContainers is updated to .NET 5 "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**.
@ -86,7 +86,7 @@ eShopOnContainers is updated to .NET 7 "wave" of technologies. Not just compilat
### Architecture overview
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.
This reference application is cross-platform at the server and client-side, thanks to .NET 5 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).
![](img/eshop_logo.png)

View File

@ -2,12 +2,8 @@
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 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-3.1.1`: Contains the code changes specific to the `.NET 3.1`
> [!DISCLAIMER]: The `main` branch contains the old code base and will get obsolete in the future. So it's recommended to refer to different [tags](https://github.com/dotnet-architecture/eShopOnContainers/tags) to avoid any confusion.
- `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 5`
- `main`: Synced time to time from `dev`.It contains "stable" code, although not the latest one. Right now, this branch contains changes specific to `.NET Core 3.1`
Any other branch is considered temporary and could be deleted at any time. Do not submit any PR against them!

View File

@ -31,3 +31,5 @@ $services |% {
Write-Host "Setting ACR build $bname ($bimg)"
az acr build-task create --registry $acrName --name $bname --image ${bimg}:$gitBranch --context $gitContext --branch $gitBranch --git-access-token $patToken --file $bfile
}
# Basket.API

View File

@ -9,20 +9,19 @@ static_resources:
- address:
socket_address:
address: 0.0.0.0
port_value: 8080
port_value: 80
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: eshop_backend_route
virtual_hosts:
- name: eshop_backend
domains:
- ["*"]
- "*"
routes:
- name: "c-short"
match:
@ -78,70 +77,63 @@ static_resources:
prefix_rewrite: "/"
cluster: shoppingagg
http_filters:
- name: envoy.filters.http.router
- name: envoy.router
access_log:
- name: envoy.file_access_log
filter:
not_health_check_filter: {}
config:
json_format:
time: "%START_TIME%"
protocol: "%PROTOCOL%"
duration: "%DURATION%"
request_method: "%REQ(:METHOD)%"
request_host: "%REQ(HOST)%"
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
response_flags: "%RESPONSE_FLAGS%"
route_name: "%ROUTE_NAME%"
upstream_host: "%UPSTREAM_HOST%"
upstream_cluster: "%UPSTREAM_CLUSTER%"
upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%"
path: "/tmp/access.log"
clusters:
- name: shoppingagg
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: shoppingagg
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
hosts:
- socket_address:
address: webshoppingagg
port_value: 80
- name: catalog
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: catalog
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
hosts:
- socket_address:
address: catalog-api
port_value: 80
- name: basket
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: basket
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
hosts:
- socket_address:
address: basket-api
port_value: 80
- name: ordering
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: ordering
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
hosts:
- socket_address:
address: ordering-api
port_value: 80
- name: signalr-hub
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: signalr-hub
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
hosts:
- socket_address:
address: ordering-signalrhub
port_value: 80

View File

@ -27,10 +27,6 @@ spec:
linkerd.io/inject: enabled
{{- end }}
spec:
securityContext:
runAsUser: 2000
runAsGroup: 3000
fsGroup: 2000
{{ if .Values.inf.registry -}}
imagePullSecrets:
- name: {{ .Values.inf.registry.secretName }}
@ -92,7 +88,7 @@ spec:
{{- end }}
ports:
- name: http
containerPort: 8080
containerPort: 80
protocol: TCP
- name: admin
containerPort: 8001

View File

@ -39,7 +39,7 @@ spec:
- host: {{ . }}
http:
paths:
- path: {{ $ingressPath }}(/|$)(.*)
- path: {{ $ingressPath }}
pathType: Prefix
backend:
service:

View File

@ -4,7 +4,7 @@ pathBase: /mobileshoppingapigw
image:
repository: envoyproxy/envoy
tag: v1.21.0
tag: v1.11.1
service:
type: ClusterIP
@ -14,9 +14,8 @@ service:
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: "/"
ingress.kubernetes.io/rewrite-target: "/"
tls: []
resources: {}

View File

@ -9,20 +9,19 @@ static_resources:
- address:
socket_address:
address: 0.0.0.0
port_value: 8080
port_value: 80
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: eshop_backend_route
virtual_hosts:
- name: eshop_backend
domains:
- ["*"]
- "*"
routes:
- name: "c-short"
match:
@ -81,70 +80,63 @@ static_resources:
prefix_rewrite: "/"
cluster: shoppingagg
http_filters:
- name: envoy.filters.http.router
- name: envoy.router
access_log:
- name: envoy.file_access_log
filter:
not_health_check_filter: {}
config:
json_format:
time: "%START_TIME%"
protocol: "%PROTOCOL%"
duration: "%DURATION%"
request_method: "%REQ(:METHOD)%"
request_host: "%REQ(HOST)%"
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
response_flags: "%RESPONSE_FLAGS%"
route_name: "%ROUTE_NAME%"
upstream_host: "%UPSTREAM_HOST%"
upstream_cluster: "%UPSTREAM_CLUSTER%"
upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%"
path: "/tmp/access.log"
clusters:
- name: shoppingagg
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: shoppingagg
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
hosts:
- socket_address:
address: webshoppingagg
port_value: 80
- name: catalog
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: catalog
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
hosts:
- socket_address:
address: catalog-api
port_value: 80
- name: basket
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: basket
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
hosts:
- socket_address:
address: basket-api
port_value: 80
- name: ordering
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: ordering
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
hosts:
- socket_address:
address: ordering-api
port_value: 80
- name: signalr-hub
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: signalr-hub
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
hosts:
- socket_address:
address: ordering-signalrhub
port_value: 80

View File

@ -26,10 +26,6 @@ spec:
linkerd.io/inject: enabled
{{- end }}
spec:
securityContext:
runAsUser: 2000
runAsGroup: 3000
fsGroup: 2000
{{ if .Values.inf.registry -}}
imagePullSecrets:
- name: {{ .Values.inf.registry.secretName }}
@ -91,7 +87,7 @@ spec:
{{- end }}
ports:
- name: http
containerPort: 8080
containerPort: 80
protocol: TCP
- name: admin
containerPort: 8001

View File

@ -38,7 +38,7 @@ spec:
- host: {{ . }}
http:
paths:
- path: {{ $ingressPath }}(/|$)(.*)
- path: {{ $ingressPath }}
pathType: Prefix
backend:
service:

View File

@ -4,7 +4,7 @@ pathBase: /webshoppingapigw
image:
repository: envoyproxy/envoy
tag: v1.21.0
tag: v1.11.1
service:
type: ClusterIP
@ -14,9 +14,8 @@ service:
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: "/"
ingress.kubernetes.io/rewrite-target: "/"
tls: []
resources: {}

View File

@ -13,7 +13,7 @@ metadata:
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
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 }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
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__PicBaseUrl: {{ $protocol }}://{{ $webshoppingapigw }}/c/api/v1/catalog/items/[0]/pic/
catalog__AzureStorageEnabled: "{{ .Values.inf.misc.useAzureStorage }}"
all__EventBusConnection: {{ .Values.inf.eventbus.constr }}

View File

@ -20,7 +20,7 @@ metadata:
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
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 }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
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__keystore: {{ .Values.inf.redis.keystore.constr }}
all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}"
mvc_e: http://{{ $mvc_url }}

View File

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

View File

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

View File

@ -12,7 +12,7 @@ metadata:
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
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 }};TrustServerCertificate={{ .Values.inf.sql.common.TrustServerCertificate }};
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__EnableLoadTest: "{{ .Values.inf.misc.useLoadTest }}"
all__EventBusConnection: {{ .Values.inf.eventbus.constr }}
all__InstrumentationKey: "{{ .Values.inf.appinsights.key }}"

View File

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

View File

@ -4,7 +4,7 @@ metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
name: ingress-nginx-controller
name: nginx-configuration
namespace: ingress-nginx
data:
proxy-buffer-size: "128k"

View File

@ -11,7 +11,6 @@ metadata:
namespace: default
spec:
rules:
- host: localhost
http:
paths:
- path: /webmvc
@ -35,7 +34,6 @@ metadata:
namespace: default
spec:
rules:
- host: localhost
http:
paths:
- path: /identity

Binary file not shown.

View File

@ -6,15 +6,15 @@
# Use this values to run the app locally in Windows
ESHOP_EXTERNAL_DNS_NAME_OR_IP=host.docker.internal
ESHOP_STORAGE_CATALOG_URL=http://host.docker.internal:5121/c/api/v1/catalog/items/[0]/pic/
ESHOP_STORAGE_CATALOG_URL=http://host.docker.internal:5202/c/api/v1/catalog/items/[0]/pic/
# Use this values to run the app locally in Mac
# ESHOP_EXTERNAL_DNS_NAME_OR_IP=docker.for.mac.localhost
# ESHOP_STORAGE_CATALOG_URL=http://docker.for.mac.localhost:5121/c/api/v1/catalog/items/[0]/pic/
# ESHOP_STORAGE_CATALOG_URL=http://docker.for.mac.localhost:5202/c/api/v1/catalog/items/[0]/pic/
# Use this values to run the app locally in Linux
# ESHOP_EXTERNAL_DNS_NAME_OR_IP=docker.for.linux.localhost
# ESHOP_STORAGE_CATALOG_URL=http://docker.for.linux.localhost:5121/c/api/v1/catalog/items/[0]/pic/
# ESHOP_STORAGE_CATALOG_URL=http://docker.for.linux.localhost:5202/c/api/v1/catalog/items/[0]/pic/
# Configure this values to the cloud storage locations
# ESHOP_STORAGE_CATALOG_URL=<YourAzureStorage_Catalog_BLOB_URL>

View File

@ -0,0 +1,139 @@
admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 8001
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 80
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
route_config:
name: eshop_backend_route
virtual_hosts:
- name: eshop_backend
domains:
- "*"
routes:
- name: "c-short"
match:
prefix: "/c/"
route:
auto_host_rewrite: true
prefix_rewrite: "/catalog-api/"
cluster: catalog
- name: "c-long"
match:
prefix: "/catalog-api/"
route:
auto_host_rewrite: true
cluster: catalog
- name: "o-short"
match:
prefix: "/o/"
route:
auto_host_rewrite: true
prefix_rewrite: "/ordering-api/"
cluster: ordering
- name: "o-long"
match:
prefix: "/ordering-api/"
route:
auto_host_rewrite: true
cluster: ordering
- name: "h-long"
match:
prefix: "/hub/notificationhub"
route:
auto_host_rewrite: true
cluster: signalr-hub
timeout: 300s
- name: "b-short"
match:
prefix: "/b/"
route:
auto_host_rewrite: true
prefix_rewrite: "/basket-api/"
cluster: basket
- name: "b-long"
match:
prefix: "/basket-api/"
route:
auto_host_rewrite: true
cluster: basket
- name: "agg"
match:
prefix: "/"
route:
auto_host_rewrite: true
prefix_rewrite: "/"
cluster: shoppingagg
http_filters:
- name: envoy.router
access_log:
- name: envoy.file_access_log
filter:
not_health_check_filter: {}
config:
json_format:
time: "%START_TIME%"
protocol: "%PROTOCOL%"
duration: "%DURATION%"
request_method: "%REQ(:METHOD)%"
request_host: "%REQ(HOST)%"
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
response_flags: "%RESPONSE_FLAGS%"
route_name: "%ROUTE_NAME%"
upstream_host: "%UPSTREAM_HOST%"
upstream_cluster: "%UPSTREAM_CLUSTER%"
upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%"
path: "/tmp/access.log"
clusters:
- name: shoppingagg
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: mobileshoppingagg
port_value: 80
- name: catalog
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: catalog-api
port_value: 80
- name: basket
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: basket-api
port_value: 80
- name: ordering
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: ordering-api
port_value: 80
- name: signalr-hub
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: ordering-signalrhub
port_value: 80

View File

@ -0,0 +1,142 @@
admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 8001
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 80
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
route_config:
name: eshop_backend_route
virtual_hosts:
- name: eshop_backend
domains:
- "*"
routes:
- name: "c-short"
match:
prefix: "/c/"
route:
auto_host_rewrite: true
prefix_rewrite: "/catalog-api/"
cluster: catalog
- name: "c-long"
match:
prefix: "/catalog-api/"
route:
auto_host_rewrite: true
cluster: catalog
- name: "o-short"
match:
prefix: "/o/"
route:
auto_host_rewrite: true
prefix_rewrite: "/ordering-api/"
cluster: ordering
- name: "o-long"
match:
prefix: "/ordering-api/"
route:
auto_host_rewrite: true
cluster: ordering
- name: "h-long"
match:
prefix: "/hub/notificationhub"
route:
auto_host_rewrite: true
cluster: signalr-hub
timeout: 300s
upgrade_configs:
upgrade_type: "websocket"
enabled: true
- name: "b-short"
match:
prefix: "/b/"
route:
auto_host_rewrite: true
prefix_rewrite: "/basket-api/"
cluster: basket
- name: "b-long"
match:
prefix: "/basket-api/"
route:
auto_host_rewrite: true
cluster: basket
- name: "agg"
match:
prefix: "/"
route:
auto_host_rewrite: true
prefix_rewrite: "/"
cluster: shoppingagg
http_filters:
- name: envoy.router
access_log:
- name: envoy.file_access_log
filter:
not_health_check_filter: {}
config:
json_format:
time: "%START_TIME%"
protocol: "%PROTOCOL%"
duration: "%DURATION%"
request_method: "%REQ(:METHOD)%"
request_host: "%REQ(HOST)%"
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
response_flags: "%RESPONSE_FLAGS%"
route_name: "%ROUTE_NAME%"
upstream_host: "%UPSTREAM_HOST%"
upstream_cluster: "%UPSTREAM_CLUSTER%"
upstream_local_address: "%UPSTREAM_LOCAL_ADDRESS%"
path: "/tmp/access.log"
clusters:
- name: shoppingagg
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: webshoppingagg
port_value: 80
- name: catalog
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: catalog-api
port_value: 80
- name: basket
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: basket-api
port_value: 80
- name: ordering
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: ordering-api
port_value: 80
- name: signalr-hub
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
- socket_address:
address: ordering-signalrhub
port_value: 80

View File

@ -1,7 +1,9 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
using System.Collections.Generic;
public class UrlsConfig
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config
{
public class UrlsConfig
{
public class CatalogOperations
{
public static string GetItemById(int id) => $"/api/v1/catalog/items/{id}";
@ -32,4 +34,5 @@ public class UrlsConfig
public string GrpcCatalog { get; set; }
public string GrpcOrdering { get; set; }
}
}

View File

@ -1,10 +1,19 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
[Route("api/v1/[controller]")]
[Authorize]
[ApiController]
public class BasketController : ControllerBase
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers
{
[Route("api/v1/[controller]")]
[Authorize]
[ApiController]
public class BasketController : ControllerBase
{
private readonly ICatalogService _catalog;
private readonly IBasketService _basket;
@ -16,7 +25,8 @@ public class BasketController : ControllerBase
[HttpPost]
[HttpPut]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)]
public async Task<ActionResult<BasketData>> UpdateAllBasketAsync([FromBody] UpdateBasketRequest data)
{
if (data.Items == null || !data.Items.Any())
@ -25,7 +35,8 @@ public class BasketController : ControllerBase
}
// Retrieve the current basket
var basket = await _basket.GetByIdAsync(data.BuyerId) ?? new BasketData(data.BuyerId);
var basket = await _basket.GetById(data.BuyerId) ?? new BasketData(data.BuyerId);
var catalogItems = await _catalog.GetCatalogItemsAsync(data.Items.Select(x => x.ProductId));
// group by product id to avoid duplicates
var itemsCalculated = data
@ -72,7 +83,8 @@ public class BasketController : ControllerBase
[HttpPut]
[Route("items")]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)]
public async Task<ActionResult<BasketData>> UpdateQuantitiesAsync([FromBody] UpdateBasketItemsRequest data)
{
if (!data.Updates.Any())
@ -81,7 +93,7 @@ public class BasketController : ControllerBase
}
// Retrieve the current basket
var currentBasket = await _basket.GetByIdAsync(data.BasketId);
var currentBasket = await _basket.GetById(data.BasketId);
if (currentBasket == null)
{
return BadRequest($"Basket with id {data.BasketId} not found.");
@ -108,8 +120,8 @@ public class BasketController : ControllerBase
[HttpPost]
[Route("items")]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.OK)]
public async Task<ActionResult> AddBasketItemAsync([FromBody] AddBasketItemRequest data)
{
if (data == null || data.Quantity == 0)
@ -123,7 +135,7 @@ public class BasketController : ControllerBase
//item.PictureUri =
// Step 2: Get current basket status
var currentBasket = (await _basket.GetByIdAsync(data.BasketId)) ?? new BasketData(data.BasketId);
var currentBasket = (await _basket.GetById(data.BasketId)) ?? new BasketData(data.BasketId);
// Step 3: Merge current status with new product
currentBasket.Items.Add(new BasketDataItem()
{
@ -140,4 +152,5 @@ public class BasketController : ControllerBase
return Ok();
}
}
}

View File

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

View File

@ -1,10 +1,17 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using System.Net;
using System.Threading.Tasks;
[Route("api/v1/[controller]")]
[Authorize]
[ApiController]
public class OrderController : ControllerBase
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers
{
[Route("api/v1/[controller]")]
[Authorize]
[ApiController]
public class OrderController : ControllerBase
{
private readonly IBasketService _basketService;
private readonly IOrderingService _orderingService;
@ -16,7 +23,8 @@ public class OrderController : ControllerBase
[Route("draft/{basketId}")]
[HttpGet]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(OrderData), (int)HttpStatusCode.OK)]
public async Task<ActionResult<OrderData>> GetOrderDraftAsync(string basketId)
{
if (string.IsNullOrEmpty(basketId))
@ -24,7 +32,7 @@ public class OrderController : ControllerBase
return BadRequest("Need a valid basketid");
}
// Get the basket data and build a order draft based on it
var basket = await _basketService.GetByIdAsync(basketId);
var basket = await _basketService.GetById(basketId);
if (basket == null)
{
@ -33,4 +41,5 @@ public class OrderController : ControllerBase
return await _orderingService.GetOrderDraftAsync(basket);
}
}
}

View File

@ -1,8 +1,8 @@
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
# It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles
@ -11,6 +11,7 @@ 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/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.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj"
COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj"
@ -32,8 +33,6 @@ 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.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/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj"
COPY "Services/Contact/Contact.API/Contact.API.csproj" "Services/Contact/Contact.API/Contact.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 "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj"
@ -43,7 +42,6 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj"
COPY "docker-compose.dcproj" "docker-compose.dcproj"
COPY "Directory.Packages.props" "Directory.Packages.props"
COPY "NuGet.config" "NuGet.config"
RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln"

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0
FROM mcr.microsoft.com/dotnet/sdk:5.0
ARG BUILD_CONFIGURATION=Debug
ENV ASPNETCORE_ENVIRONMENT=Development
ENV DOTNET_USE_POLLING_FILE_WATCHER=true
@ -6,6 +6,7 @@ EXPOSE 80
WORKDIR /src
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"]
RUN dotnet restore src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503

View File

@ -1,62 +0,0 @@
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;
}
}

View File

@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using System.Linq;
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 OpenApiSecurityRequirement
{
[ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator" }
}
};
}
}
}
}

View File

@ -1,13 +0,0 @@
global using System.Text.Json;
global using CatalogApi;
global using Grpc.Core;
global using Grpc.Core.Interceptors;
global using GrpcBasket;
global using Microsoft.AspNetCore.Authorization;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure;
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
global using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
global using Microsoft.Extensions.Options;
global using Services.Common;

View File

@ -1,7 +1,12 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
public class GrpcExceptionInterceptor : Interceptor
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure
{
public class GrpcExceptionInterceptor : Interceptor
{
private readonly ILogger<GrpcExceptionInterceptor> _logger;
public GrpcExceptionInterceptor(ILogger<GrpcExceptionInterceptor> logger)
@ -28,8 +33,9 @@ public class GrpcExceptionInterceptor : Interceptor
}
catch (RpcException e)
{
_logger.LogError(e, "Error calling via gRPC: {Status}", e.Status);
_logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message);
return default;
}
}
}
}

View File

@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
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);
}
}
}

View File

@ -1,23 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<AssemblyName>Mobile.Shopping.HttpAggregator</AssemblyName>
<RootNamespace>Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator</RootNamespace>
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<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" />
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Services\Services.Common\Services.Common.csproj" />
<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.14.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" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\Devspaces.Support\Devspaces.Support.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -1,7 +1,7 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
public class AddBasketItemRequest
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
{
public class AddBasketItemRequest
{
public int CatalogItemId { get; set; }
public string BasketId { get; set; }
@ -12,4 +12,5 @@ public class AddBasketItemRequest
{
Quantity = 1;
}
}
}

View File

@ -1,10 +1,13 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Collections.Generic;
public class BasketData
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
{
public class BasketData
{
public string BuyerId { get; set; }
public List<BasketDataItem> Items { get; set; } = new();
public List<BasketDataItem> Items { get; set; } = new List<BasketDataItem>();
public BasketData()
{
@ -14,4 +17,6 @@ public class BasketData
{
BuyerId = buyerId;
}
}
}

View File

@ -1,7 +1,8 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
public class BasketDataItem
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
{
public class BasketDataItem
{
public string Id { get; set; }
public int ProductId { get; set; }
@ -15,4 +16,6 @@ public class BasketDataItem
public int Quantity { get; set; }
public string PictureUrl { get; set; }
}
}

View File

@ -1,7 +1,7 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
public class CatalogItem
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
{
public class CatalogItem
{
public int Id { get; set; }
public string Name { get; set; }
@ -9,4 +9,5 @@ public class CatalogItem
public decimal Price { get; set; }
public string PictureUri { get; set; }
}
}

View File

@ -1,7 +1,11 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System;
using System.Collections.Generic;
public class OrderData
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
{
public class OrderData
{
public string OrderNumber { get; set; }
public DateTime Date { get; set; }
@ -38,5 +42,7 @@ public class OrderData
public string Buyer { get; set; }
public List<OrderItemData> OrderItems { get; } = new();
public List<OrderItemData> OrderItems { get; } = new List<OrderItemData>();
}
}

View File

@ -1,7 +1,8 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
public class OrderItemData
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
{
public class OrderItemData
{
public int ProductId { get; set; }
public string ProductName { get; set; }
@ -13,4 +14,6 @@ public class OrderItemData
public int Units { get; set; }
public string PictureUrl { get; set; }
}
}

View File

@ -1,7 +1,8 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
public class UpdateBasketItemData
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
{
public class UpdateBasketItemData
{
public string BasketItemId { get; set; }
public int NewQty { get; set; }
@ -10,4 +11,6 @@ public class UpdateBasketItemData
{
NewQty = 0;
}
}
}

View File

@ -1,7 +1,11 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Collections.Generic;
public class UpdateBasketItemsRequest
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
{
public class UpdateBasketItemsRequest
{
public string BasketId { get; set; }
public ICollection<UpdateBasketItemData> Updates { get; set; }
@ -10,4 +14,6 @@ public class UpdateBasketItemsRequest
{
Updates = new List<UpdateBasketItemData>();
}
}
}

View File

@ -1,8 +1,13 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Collections.Generic;
public class UpdateBasketRequest
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
{
public class UpdateBasketRequest
{
public string BuyerId { get; set; }
public IEnumerable<UpdateBasketRequestItemData> Items { get; set; }
}
}

View File

@ -1,10 +1,13 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
public class UpdateBasketRequestItemData
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models
{
public class UpdateBasketRequestItemData
{
public string Id { get; set; } // Basket id
public int ProductId { get; set; } // Catalog item id
public int Quantity { get; set; } // Quantity
}
}

View File

@ -1,24 +1,29 @@
var builder = WebApplication.CreateBuilder(args);
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator;
using Serilog;
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();
BuildWebHost(args).Run();
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();

View File

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

View File

@ -1,7 +1,13 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using GrpcBasket;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Threading.Tasks;
public class BasketService : IBasketService
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
{
public class BasketService : IBasketService
{
private readonly Basket.BasketClient _basketClient;
private readonly ILogger<BasketService> _logger;
@ -11,7 +17,7 @@ public class BasketService : IBasketService
_logger = logger;
}
public async Task<BasketData> GetByIdAsync(string id)
public async Task<BasketData> GetById(string id)
{
_logger.LogDebug("grpc client created, request = {@id}", id);
var response = await _basketClient.GetBasketByIdAsync(new BasketRequest { Id = id });
@ -80,4 +86,5 @@ public class BasketService : IBasketService
return map;
}
}
}

View File

@ -1,7 +1,13 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using CatalogApi;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class CatalogService : ICatalogService
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
{
public class CatalogService : ICatalogService
{
private readonly Catalog.CatalogClient _client;
public CatalogService(Catalog.CatalogClient client)
@ -33,4 +39,5 @@ public class CatalogService : ICatalogService
Price = (decimal)catalogItemResponse.Price
};
}
}
}

View File

@ -1,8 +1,13 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Threading.Tasks;
public interface IBasketService
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
{
Task<BasketData> GetByIdAsync(string id);
public interface IBasketService
{
Task<BasketData> GetById(string id);
Task UpdateAsync(BasketData currentBasket);
}
}

View File

@ -1,8 +1,13 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
public interface ICatalogService
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
{
public interface ICatalogService
{
Task<CatalogItem> GetCatalogItemAsync(int id);
Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids);
}
}

View File

@ -1,6 +1,10 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Threading.Tasks;
public interface IOrderApiClient
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
{
public interface IOrderApiClient
{
Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket);
}
}

View File

@ -1,6 +1,10 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using System.Threading.Tasks;
public interface IOrderingService
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
{
public interface IOrderingService
{
Task<OrderData> GetOrderDraftAsync(BasketData basketData);
}
}

View File

@ -1,7 +1,15 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.Json;
public class OrderApiClient : IOrderApiClient
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
{
public class OrderApiClient : IOrderApiClient
{
private readonly HttpClient _apiClient;
private readonly ILogger<OrderApiClient> _logger;
private readonly UrlsConfig _urls;
@ -23,6 +31,10 @@ public class OrderApiClient : IOrderApiClient
var ordersDraftResponse = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<OrderData>(ordersDraftResponse, JsonDefaults.CaseInsensitiveOptions);
return JsonSerializer.Deserialize<OrderData>(ordersDraftResponse, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
}
}
}

View File

@ -1,11 +1,17 @@
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using GrpcOrdering;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Threading.Tasks;
public class OrderingService : IOrderingService
namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services
{
private readonly GrpcOrdering.OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
public class OrderingService : IOrderingService
{
private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
private readonly ILogger<OrderingService> _logger;
public OrderingService(GrpcOrdering.OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
{
_orderingGrpcClient = orderingGrpcClient;
_logger = logger;
@ -48,14 +54,14 @@ public class OrderingService : IOrderingService
return data;
}
private GrpcOrdering.CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
{
var command = new GrpcOrdering.CreateOrderDraftCommand
var command = new CreateOrderDraftCommand
{
BuyerId = basketData.BuyerId,
};
basketData.Items.ForEach(i => command.Items.Add(new GrpcOrdering.BasketItem
basketData.Items.ForEach(i => command.Items.Add(new BasketItem
{
Id = i.Id,
OldUnitPrice = (double)i.OldUnitPrice,
@ -69,4 +75,5 @@ public class OrderingService : IOrderingService
return command;
}
}
}

View File

@ -0,0 +1,222 @@
using CatalogApi;
using Devspaces.Support;
using GrpcBasket;
using GrpcOrdering;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure;
using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
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;
}
}
}

View File

@ -1,138 +1,15 @@
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"System.Net.Http": "Warning"
"Default": "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}"
}
}
},
"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"
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
},
"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"
}

View File

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

View File

@ -0,0 +1,55 @@
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}

View File

@ -1,8 +1,11 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
using System.Collections.Generic;
public class UrlsConfig
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config
{
public class UrlsConfig
{
public class CatalogOperations
{
// grpc call under REST must go trough port 80
@ -37,5 +40,6 @@ public class UrlsConfig
public string GrpcCatalog { get; set; }
public string GrpcOrdering { get; set; }
}
}
}

View File

@ -1,10 +1,19 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
[Route("api/v1/[controller]")]
[Authorize]
[ApiController]
public class BasketController : ControllerBase
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers
{
[Route("api/v1/[controller]")]
[Authorize]
[ApiController]
public class BasketController : ControllerBase
{
private readonly ICatalogService _catalog;
private readonly IBasketService _basket;
@ -16,7 +25,8 @@ public class BasketController : ControllerBase
[HttpPost]
[HttpPut]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)]
public async Task<ActionResult<BasketData>> UpdateAllBasketAsync([FromBody] UpdateBasketRequest data)
{
if (data.Items == null || !data.Items.Any())
@ -25,7 +35,7 @@ public class BasketController : ControllerBase
}
// Retrieve the current basket
var basket = await _basket.GetByIdAsync(data.BuyerId) ?? new BasketData(data.BuyerId);
var basket = await _basket.GetById(data.BuyerId) ?? new BasketData(data.BuyerId);
var catalogItems = await _catalog.GetCatalogItemsAsync(data.Items.Select(x => x.ProductId));
// group by product id to avoid duplicates
@ -73,7 +83,8 @@ public class BasketController : ControllerBase
[HttpPut]
[Route("items")]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(BasketData), (int)HttpStatusCode.OK)]
public async Task<ActionResult<BasketData>> UpdateQuantitiesAsync([FromBody] UpdateBasketItemsRequest data)
{
if (!data.Updates.Any())
@ -82,7 +93,7 @@ public class BasketController : ControllerBase
}
// Retrieve the current basket
var currentBasket = await _basket.GetByIdAsync(data.BasketId);
var currentBasket = await _basket.GetById(data.BasketId);
if (currentBasket == null)
{
return BadRequest($"Basket with id {data.BasketId} not found.");
@ -107,8 +118,8 @@ public class BasketController : ControllerBase
[HttpPost]
[Route("items")]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.OK)]
public async Task<ActionResult> AddBasketItemAsync([FromBody] AddBasketItemRequest data)
{
if (data == null || data.Quantity == 0)
@ -122,7 +133,7 @@ public class BasketController : ControllerBase
//item.PictureUri =
// Step 2: Get current basket status
var currentBasket = (await _basket.GetByIdAsync(data.BasketId)) ?? new BasketData(data.BasketId);
var currentBasket = (await _basket.GetById(data.BasketId)) ?? new BasketData(data.BasketId);
// Step 3: Search if exist product into basket
var product = currentBasket.Items.SingleOrDefault(i => i.ProductId == item.Id);
if (product != null)
@ -149,4 +160,5 @@ public class BasketController : ControllerBase
return Ok();
}
}
}

View File

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

View File

@ -1,13 +1,19 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using System.Net;
using System.Threading.Tasks;
[Route("api/v1/[controller]")]
[Authorize]
[ApiController]
public class OrderController : ControllerBase
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Controllers
{
[Route("api/v1/[controller]")]
[Authorize]
[ApiController]
public class OrderController : ControllerBase
{
private readonly IBasketService _basketService;
private readonly IOrderingService _orderingService;
public OrderController(IBasketService basketService, IOrderingService orderingService)
{
_basketService = basketService;
@ -16,15 +22,16 @@ public class OrderController : ControllerBase
[Route("draft/{basketId}")]
[HttpGet]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(OrderData), (int)HttpStatusCode.OK)]
public async Task<ActionResult<OrderData>> GetOrderDraftAsync(string basketId)
{
if (string.IsNullOrWhiteSpace(basketId))
if (string.IsNullOrEmpty(basketId))
{
return BadRequest("Need a valid basketid");
}
// Get the basket data and build a order draft based on it
var basket = await _basketService.GetByIdAsync(basketId);
var basket = await _basketService.GetById(basketId);
if (basket == null)
{
@ -33,4 +40,5 @@ public class OrderController : ControllerBase
return await _orderingService.GetOrderDraftAsync(basket);
}
}
}

View File

@ -1,8 +1,8 @@
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
# It's important to keep lines from here down to "COPY . ." identical in all Dockerfiles
@ -11,6 +11,7 @@ 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/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.Tests/EventBus.Tests.csproj" "BuildingBlocks/EventBus/EventBus.Tests/EventBus.Tests.csproj"
COPY "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj"
@ -32,8 +33,6 @@ 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.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/Services.Common/Services.Common.csproj" "Services/Services.Common/Services.Common.csproj"
COPY "Services/Contact/Contact.API/Contact.API.csproj" "Services/Contact/Contact.API/Contact.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 "Web/WebhookClient/WebhookClient.csproj" "Web/WebhookClient/WebhookClient.csproj"
@ -43,7 +42,6 @@ COPY "Web/WebStatus/WebStatus.csproj" "Web/WebStatus/WebStatus.csproj"
COPY "docker-compose.dcproj" "docker-compose.dcproj"
COPY "Directory.Packages.props" "Directory.Packages.props"
COPY "NuGet.config" "NuGet.config"
RUN dotnet restore "eShopOnContainers-ServicesAndWebApps.sln"

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0
FROM mcr.microsoft.com/dotnet/sdk:5.0
ARG BUILD_CONFIGURATION=Debug
ENV ASPNETCORE_ENVIRONMENT=Development
ENV DOTNET_USE_POLLING_FILE_WATCHER=true
@ -6,6 +6,7 @@ EXPOSE 80
WORKDIR /src
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"]
RUN dotnet restore src/ApiGateways/Web.Bff.Shopping/aggregator/Web.Shopping.HttpAggregator.csproj -nowarn:msb3202,nu1503

View File

@ -1,63 +0,0 @@
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;
}
}

View File

@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Collections.Generic;
using System.Linq;
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 OpenApiSecurityRequirement
{
[ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator" }
}
};
}
}
}
}

View File

@ -1,13 +0,0 @@
global using System.Text.Json;
global using CatalogApi;
global using Grpc.Core;
global using Grpc.Core.Interceptors;
global using GrpcBasket;
global using Microsoft.AspNetCore.Authorization;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure;
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
global using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
global using Microsoft.Extensions.Options;
global using Services.Common;

View File

@ -1,7 +1,12 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
public class GrpcExceptionInterceptor : Interceptor
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure
{
public class GrpcExceptionInterceptor : Interceptor
{
private readonly ILogger<GrpcExceptionInterceptor> _logger;
public GrpcExceptionInterceptor(ILogger<GrpcExceptionInterceptor> logger)
@ -19,17 +24,18 @@ public class GrpcExceptionInterceptor : Interceptor
return new AsyncUnaryCall<TResponse>(HandleResponse(call.ResponseAsync), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose);
}
private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> task)
private async Task<TResponse> HandleResponse<TResponse>(Task<TResponse> t)
{
try
{
var response = await task;
var response = await t;
return response;
}
catch (RpcException e)
{
_logger.LogError(e, "Error calling via gRPC: {Status}", e.Status);
_logger.LogError("Error calling via grpc: {Status} - {Message}", e.Status, e.Message);
return default;
}
}
}
}

View File

@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
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.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);
}
}
}

View File

@ -1,7 +1,8 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
public class AddBasketItemRequest
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class AddBasketItemRequest
{
public int CatalogItemId { get; set; }
public string BasketId { get; set; }
@ -12,5 +13,6 @@ public class AddBasketItemRequest
{
Quantity = 1;
}
}
}
}

View File

@ -1,10 +1,13 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System.Collections.Generic;
public class BasketData
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class BasketData
{
public string BuyerId { get; set; }
public List<BasketDataItem> Items { get; set; } = new();
public List<BasketDataItem> Items { get; set; } = new List<BasketDataItem>();
public BasketData()
{
@ -14,5 +17,6 @@ public class BasketData
{
BuyerId = buyerId;
}
}
}
}

View File

@ -1,7 +1,8 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
public class BasketDataItem
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class BasketDataItem
{
public string Id { get; set; }
public int ProductId { get; set; }
@ -15,4 +16,6 @@ public class BasketDataItem
public int Quantity { get; set; }
public string PictureUrl { get; set; }
}
}

View File

@ -1,7 +1,8 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
public class CatalogItem
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class CatalogItem
{
public int Id { get; set; }
public string Name { get; set; }
@ -9,6 +10,6 @@ public class CatalogItem
public decimal Price { get; set; }
public string PictureUri { get; set; }
}
}

View File

@ -1,7 +1,11 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System;
using System.Collections.Generic;
public class OrderData
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class OrderData
{
public string OrderNumber { get; set; }
public DateTime Date { get; set; }
@ -38,6 +42,7 @@ public class OrderData
public string Buyer { get; set; }
public List<OrderItemData> OrderItems { get; } = new();
}
public List<OrderItemData> OrderItems { get; } = new List<OrderItemData>();
}
}

View File

@ -1,7 +1,8 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
public class OrderItemData
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class OrderItemData
{
public int ProductId { get; set; }
public string ProductName { get; set; }
@ -13,4 +14,6 @@ public class OrderItemData
public int Units { get; set; }
public string PictureUrl { get; set; }
}
}

View File

@ -1,9 +1,16 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
public class UpdateBasketItemData
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class UpdateBasketItemData
{
public string BasketItemId { get; set; }
public int NewQty { get; set; }
public UpdateBasketItemData()
{
NewQty = 0;
}
}
}

View File

@ -1,7 +1,10 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System.Collections.Generic;
public class UpdateBasketItemsRequest
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class UpdateBasketItemsRequest
{
public string BasketId { get; set; }
public ICollection<UpdateBasketItemData> Updates { get; set; }
@ -10,4 +13,6 @@ public class UpdateBasketItemsRequest
{
Updates = new List<UpdateBasketItemData>();
}
}
}

View File

@ -1,8 +1,13 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System.Collections.Generic;
public class UpdateBasketRequest
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class UpdateBasketRequest
{
public string BuyerId { get; set; }
public IEnumerable<UpdateBasketRequestItemData> Items { get; set; }
}
}

View File

@ -1,10 +1,11 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
public class UpdateBasketRequestItemData
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models
{
public class UpdateBasketRequestItemData
{
public string Id { get; set; } // Basket id
public int ProductId { get; set; } // Catalog item id
public int Quantity { get; set; } // Quantity
}
}

View File

@ -1,38 +1,29 @@
var builder = WebApplication.CreateBuilder(args);
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator;
using Serilog;
builder.AddServiceDefaults();
BuildWebHost(args).Run();
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();
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();

View File

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

View File

@ -1,7 +1,13 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using GrpcBasket;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Threading.Tasks;
public class BasketService : IBasketService
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public class BasketService : IBasketService
{
private readonly Basket.BasketClient _basketClient;
private readonly ILogger<BasketService> _logger;
@ -11,7 +17,8 @@ public class BasketService : IBasketService
_logger = logger;
}
public async Task<BasketData> GetByIdAsync(string id)
public async Task<BasketData> GetById(string id)
{
_logger.LogDebug("grpc client created, request = {@id}", id);
var response = await _basketClient.GetBasketByIdAsync(new BasketRequest { Id = id });
@ -92,4 +99,5 @@ public class BasketService : IBasketService
return map;
}
}
}

View File

@ -1,7 +1,14 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using CatalogApi;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class CatalogService : ICatalogService
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public class CatalogService : ICatalogService
{
private readonly Catalog.CatalogClient _client;
private readonly ILogger<CatalogService> _logger;
@ -41,4 +48,5 @@ public class CatalogService : ICatalogService
Price = (decimal)catalogItemResponse.Price
};
}
}
}

View File

@ -1,8 +1,12 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System.Threading.Tasks;
public interface IBasketService
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
Task<BasketData> GetByIdAsync(string id);
public interface IBasketService
{
Task<BasketData> GetById(string id);
Task UpdateAsync(BasketData currentBasket);
}
}

View File

@ -1,8 +1,13 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
public interface ICatalogService
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public interface ICatalogService
{
Task<CatalogItem> GetCatalogItemAsync(int id);
Task<IEnumerable<CatalogItem>> GetCatalogItemsAsync(IEnumerable<int> ids);
}
}

View File

@ -1,6 +1,10 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System.Threading.Tasks;
public interface IOrderApiClient
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public interface IOrderApiClient
{
Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket);
}
}

View File

@ -1,6 +1,10 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using System.Threading.Tasks;
public interface IOrderingService
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public interface IOrderingService
{
Task<OrderData> GetOrderDraftAsync(BasketData basketData);
}
}

View File

@ -1,7 +1,15 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.Json;
public class OrderApiClient : IOrderApiClient
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
public class OrderApiClient : IOrderApiClient
{
private readonly HttpClient _apiClient;
private readonly ILogger<OrderApiClient> _logger;
private readonly UrlsConfig _urls;
@ -15,7 +23,7 @@ public class OrderApiClient : IOrderApiClient
public async Task<OrderData> GetOrderDraftFromBasketAsync(BasketData basket)
{
var url = $"{_urls.Orders}{UrlsConfig.OrdersOperations.GetOrderDraft()}";
var url = _urls.Orders + UrlsConfig.OrdersOperations.GetOrderDraft();
var content = new StringContent(JsonSerializer.Serialize(basket), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(url, content);
@ -23,6 +31,10 @@ public class OrderApiClient : IOrderApiClient
var ordersDraftResponse = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<OrderData>(ordersDraftResponse, JsonDefaults.CaseInsensitiveOptions);
return JsonSerializer.Deserialize<OrderData>(ordersDraftResponse, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
}
}
}

View File

@ -1,11 +1,17 @@
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using GrpcOrdering;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Models;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Threading.Tasks;
public class OrderingService : IOrderingService
namespace Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services
{
private readonly GrpcOrdering.OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
public class OrderingService : IOrderingService
{
private readonly OrderingGrpc.OrderingGrpcClient _orderingGrpcClient;
private readonly ILogger<OrderingService> _logger;
public OrderingService(GrpcOrdering.OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
public OrderingService(OrderingGrpc.OrderingGrpcClient orderingGrpcClient, ILogger<OrderingService> logger)
{
_orderingGrpcClient = orderingGrpcClient;
_logger = logger;
@ -48,14 +54,14 @@ public class OrderingService : IOrderingService
return data;
}
private GrpcOrdering.CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
private CreateOrderDraftCommand MapToOrderDraftCommand(BasketData basketData)
{
var command = new GrpcOrdering.CreateOrderDraftCommand
var command = new CreateOrderDraftCommand
{
BuyerId = basketData.BuyerId,
};
basketData.Items.ForEach(i => command.Items.Add(new GrpcOrdering.BasketItem
basketData.Items.ForEach(i => command.Items.Add(new BasketItem
{
Id = i.Id,
OldUnitPrice = (double)i.OldUnitPrice,
@ -69,4 +75,5 @@ public class OrderingService : IOrderingService
return command;
}
}
}

View File

@ -0,0 +1,225 @@
using CatalogApi;
using Devspaces.Support;
using GrpcBasket;
using GrpcOrdering;
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Config;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Infrastructure;
using Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
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;
}
}
}

View File

@ -1,23 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<AssemblyName>Web.Shopping.HttpAggregator</AssemblyName>
<RootNamespace>Microsoft.eShopOnContainers.Web.Shopping.HttpAggregator</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<DockerComposeProjectPath>..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
<GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<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" />
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Services\Services.Common\Services.Common.csproj" />
<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.14.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" />
<!--<PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.6.0-rc1.19456.4" />-->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\Devspaces.Support\Devspaces.Support.csproj" />
</ItemGroup>
<ItemGroup>

View File

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

View File

@ -1,138 +1,15 @@
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"System.Net.Http": "Warning"
"Default": "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}"
}
}
},
"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"
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
}
}
},
"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"
}

View File

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

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