Browse Source

Merge branch 'dev' into feature/118

pull/820/head
RamonTC 6 years ago
committed by GitHub
parent
commit
4f62175592
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
158 changed files with 18058 additions and 24220 deletions
  1. +9
    -9
      README.md
  2. BIN
      docs/Decks/BRK3175_CesarDeIaTorre.pptx
  3. BIN
      docs/Decks/eShopOnContainers-Architecture-v2.1.pptx
  4. +1
    -3
      k8s/deploy-ingress-azure.ps1
  5. +2
    -0
      k8s/deploy-ingress-dockerlocal.ps1
  6. +1
    -8
      k8s/deploy-ingress.ps1
  7. +4
    -0
      k8s/deploy.ps1
  8. +18
    -0
      k8s/helm-rbac.yaml
  9. +1
    -1
      k8s/helm/app.yaml
  10. +13
    -5
      k8s/helm/deploy-all.ps1
  11. +5
    -0
      k8s/helm/ingress_values_dockerk8s.yaml
  12. +1
    -1
      k8s/helm/ordering-signalrhub/templates/configmap.yaml
  13. +0
    -19
      k8s/nginx-ingress/azure/service.yaml
  14. +21
    -0
      k8s/nginx-ingress/cloud-generic.yaml
  15. BIN
      k8s/nginx-ingress/cm.yaml
  16. +0
    -11
      k8s/nginx-ingress/configmap.yaml
  17. +0
    -52
      k8s/nginx-ingress/default-backend.yaml
  18. +3
    -0
      k8s/nginx-ingress/local-dockerk8s/identityapi-cm-fix.yaml
  19. +3
    -0
      k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml
  20. +39
    -0
      k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml
  21. +238
    -0
      k8s/nginx-ingress/mandatory.yaml
  22. +0
    -4
      k8s/nginx-ingress/namespace.yaml
  23. +0
    -40
      k8s/nginx-ingress/patch-service-without-rbac.yaml
  24. +0
    -7
      k8s/nginx-ingress/publish-service-patch.yaml
  25. +22
    -0
      k8s/nginx-ingress/service-nodeport.yaml
  26. +0
    -5
      k8s/nginx-ingress/tcp-services-configmap.yaml
  27. +0
    -5
      k8s/nginx-ingress/udp-services-configmap.yaml
  28. +0
    -61
      k8s/nginx-ingress/without-rbac.yaml
  29. +1
    -1
      src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj
  30. +1
    -7
      src/ApiGateways/ApiGw-Base/Startup.cs
  31. +3
    -1
      src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs
  32. +4
    -2
      src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.cs
  33. +3
    -2
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/EventStateEnum.cs
  34. +14
    -1
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs
  35. +4
    -1
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs
  36. +41
    -4
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs
  37. +0
    -2
      src/Services/Basket/Basket.API/BasketSettings.cs
  38. +10
    -3
      src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs
  39. +1
    -1
      src/Services/Catalog/Catalog.API/Startup.cs
  40. +2
    -0
      src/Services/Identity/Identity.API/Controllers/AccountController.cs
  41. +0
    -1
      src/Services/Identity/Identity.API/Identity.API.csproj
  42. +1
    -1
      src/Services/Identity/Identity.API/Views/Shared/_Layout.cshtml
  43. +2
    -16
      src/Services/Identity/Identity.API/libman.json
  44. +62
    -0
      src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs
  45. +12
    -1
      src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs
  46. +21
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommand.cs
  47. +52
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs
  48. +21
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommand.cs
  49. +55
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs
  50. +21
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommand.cs
  51. +55
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs
  52. +25
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommand.cs
  53. +56
    -0
      src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs
  54. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs
  55. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs
  56. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs
  57. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs
  58. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs
  59. +1
    -1
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs
  60. +8
    -7
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs
  61. +8
    -8
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs
  62. +8
    -11
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs
  63. +8
    -11
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs
  64. +9
    -9
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs
  65. +4
    -10
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs
  66. +2
    -1
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs
  67. +25
    -15
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs
  68. +3
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs
  69. +1
    -1
      src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/ByPassAuthMiddleware.cs
  70. +1
    -1
      src/Services/Ordering/Ordering.API/Startup.cs
  71. +5
    -5
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Address.cs
  72. +49
    -1
      src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
  73. +4
    -1
      src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs
  74. +7
    -7
      src/Web/WebMVC/Views/Campaigns/Details.cshtml
  75. +20
    -16
      src/Web/WebMVC/Views/Campaigns/Index.cshtml
  76. +8
    -6
      src/Web/WebMVC/Views/Campaigns/_campaign.cshtml
  77. +1
    -1
      src/Web/WebMVC/Views/Order/Create.cshtml
  78. +23
    -23
      src/Web/WebMVC/Views/Order/Detail.cshtml
  79. +11
    -11
      src/Web/WebMVC/Views/Order/Index.cshtml
  80. +9
    -9
      src/Web/WebMVC/Views/Order/_OrderItems.cshtml
  81. +10
    -10
      src/Web/WebMVC/Views/OrderManagement/Index.cshtml
  82. +24
    -29
      src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml
  83. +11
    -12
      src/Web/WebMVC/Views/Shared/_Layout.cshtml
  84. +1
    -0
      src/Web/WebMVC/WebMVC.csproj
  85. +2
    -16
      src/Web/WebMVC/libman.json
  86. +4
    -0
      src/Web/WebMVC/wwwroot/css/app.component.css
  87. +1
    -1
      src/Web/WebMVC/wwwroot/css/app.component.min.css
  88. +4
    -0
      src/Web/WebMVC/wwwroot/css/app.component.scss
  89. +7
    -0
      src/Web/WebMVC/wwwroot/css/campaigns/campaigns.component.css
  90. +1
    -0
      src/Web/WebMVC/wwwroot/css/catalog/catalog.component.css
  91. +1
    -1
      src/Web/WebMVC/wwwroot/css/catalog/catalog.component.min.css
  92. +1
    -0
      src/Web/WebMVC/wwwroot/css/catalog/catalog.component.scss
  93. +0
    -59
      src/Web/WebSPA/.angular-cli.json
  94. +3
    -0
      src/Web/WebSPA/Client/globals.scss
  95. +5
    -6
      src/Web/WebSPA/Client/modules/app.component.html
  96. +4
    -0
      src/Web/WebSPA/Client/modules/app.component.scss
  97. +6
    -8
      src/Web/WebSPA/Client/modules/app.component.ts
  98. +11
    -14
      src/Web/WebSPA/Client/modules/app.module.ts
  99. +1
    -1
      src/Web/WebSPA/Client/modules/basket/basket-status/basket-status.component.ts
  100. +16
    -16
      src/Web/WebSPA/Client/modules/basket/basket.component.html

+ 9
- 9
README.md View File

@ -17,12 +17,12 @@ The **dockerfiles** in the solution have also been updated and now support [**Do
>**PLEASE** Read our [branch guide](./branch-guide.md) to know about our branching policy
> ### DISCLAIMER
> **IMPORTANT:** The current state of this sample application is **BETA**, because we are constantly evolving towards new released technologies. Therefore, many areas could be improved and change significantly while refactoring current code and implementing new features. Feedback with improvements and pull requests from the community will be highly appreciated and accepted.
> **IMPORTANT:** The current state of this sample application is **BETA**, because we are constantly evolving towards newly released technologies. Therefore, many areas could be improved and change significantly while refactoring the current code and implementing new features. Feedback with improvements and pull requests from the community will be highly appreciated and accepted.
>
> This reference application proposes a simplified microservice oriented architecture implementation to introduce technologies like .NET Core with Docker containers through a comprehensive application. The chosen domain is an eShop/eCommerce but simply because it is a well-know domain by most people/developers.
However, this sample application should not be considered as an "eCommerce reference model", at all. The implemented business domain might not be ideal from an eCommerce business point of view. It is neither trying to solve all the problems in a large, scalable and mission-critical distributed system. It is just a bootstrap for developers to easily get started in the world of Docker containers and microservices with .NET Core.
> This reference application proposes a simplified microservice oriented architecture implementation to introduce technologies like .NET Core with Docker containers through a comprehensive application. The chosen domain is eShop/eCommerce but simply because it is a well-known domain by most people/developers.
However, this sample application should not be considered as an "eCommerce reference model" at all. The implemented business domain might not be ideal from an eCommerce business point of view. It is neither trying to solve all the problems in a large, scalable and mission-critical distributed system. It is just a bootstrap for developers to easily get started in the world of Docker containers and microservices with .NET Core.
> <p>For example, the next step after running the solution in the local dev PC and understanding Docker containers and microservices development with .NET Core, is to select a microservice cluster/orchestrator like Kubernetes in Azure (AKS) or Azure Service Fabric, both environments tested and supported by this solution.
> Additional steps would be to move your databases to HA cloud services (like Azure SQL Database), or switch your EventBus to use Azure Service Bus (instead of bare-bone RabbitMQ) or any other production ready Service Bus in the market.
> Additional steps would be to move your databases to HA cloud services (like Azure SQL Database) or switch your EventBus to use Azure Service Bus (instead of bare-bone RabbitMQ) or any other production-ready Service Bus in the market.
![image](https://user-images.githubusercontent.com/1712635/40397331-059a7ec6-5de7-11e8-8542-a597eca16fef.png)
@ -39,7 +39,7 @@ The architecture proposes a microservice oriented architecture implementation wi
> ### Important Note on API Gateways and published APIs
> Since April 2018, we have introduced the implementation of the [API Gateway pattern](http://microservices.io/patterns/apigateway.html) and [Backend-For-Front-End (BFF) pattern](https://samnewman.io/patterns/architectural/bff/) in eShopOnContainers architecture, so you can filter and publish simplified APIs and URIs and apply additional security in that tier while hiding/securing the internal microservices to the client apps or outside consumers. These sample API Gateways in eShopOnContainers are based on [Ocelot](https://github.com/ThreeMammals/Ocelot), an OSS lightweight API Gateway solution explained [here](http://threemammals.com/ocelot). The deployed API Gateways are autonomous and can be deployed as your own custom microservices/containers, as it is currently done in eShopOnContainers, so you can test it even in a simple development environment with just Docker engine or deploy it into orchestrators like Kubernetes in AKS or Service Fabric.
> For your production-ready architecture you can either keep using [Ocelot](https://github.com/ThreeMammals/Ocelot) which is simple and easy to use and used in production by significant companies or if you need further functionality and a much richer set of features suittable for commercial APIs, you can also substitute those API Gateways and use [Azure API Management](https://azure.microsoft.com/en-us/services/api-management/) or any other commercial API Gateway, as shown in the following image.
> For your production-ready architecture you can either keep using [Ocelot](https://github.com/ThreeMammals/Ocelot) which is simple and easy to use and used in production by significant companies or if you need further functionality and a much richer set of features suitable for commercial APIs, you can also substitute those API Gateways and use [Azure API Management](https://azure.microsoft.com/en-us/services/api-management/) or any other commercial API Gateway, as shown in the following image.
<p>
<img src="img/eShopOnContainers-Architecture-With-Azure-API-Management.png">
@ -56,9 +56,9 @@ The architecture proposes a microservice oriented architecture implementation wi
<p>
> ### Important Note on Database Servers/Containers
> In this solution's current configuration for a development environment, the SQL databases are automatically deployed with sample data into a single SQL Server container (a single shared Docker container for SQL databases) so the whole solution can be up and running without any dependency to any cloud or specific server. Each database could also be deployed as a single Docker container, but then you'd need more than 8GB of RAM assigned to Docker in your development machine in order to be able to run 3 SQL Server Docker containers in your Docker Linux host in "Docker for Windows" or "Docker for Mac" development environments.
> In this solution's current configuration for a development environment, the SQL databases are automatically deployed with sample data into a single SQL Server container (a single shared Docker container for SQL databases) so the whole solution can be up and running without any dependency to any cloud or a specific server. Each database could also be deployed as a single Docker container, but then you'd need more than 8GB of RAM assigned to Docker in your development machine in order to be able to run 3 SQL Server Docker containers in your Docker Linux host in "Docker for Windows" or "Docker for Mac" development environments.
> <p> A similar case is defined in regard to Redis cache running as a container for the development environment. Or a No-SQL database (MongoDB) running as a container.
> <p> However, in a real production environment it is recommended to have your databases (SQL Server, Redis, and the NO-SQL database, in this case) in HA (High Available) services like Azure SQL Database, Redis as a service and Azure CosmosDB instead the MongoDB container (as both systems share the same access protocol). If you want to change to a production configuration, you'll just need to change the connection strings once you have set up the servers in a HA cloud or on-premises.
> <p> However, in a real production environment it is recommended to have your databases (SQL Server, Redis, and the NO-SQL database, in this case) in HA (High Available) services like Azure SQL Database, Redis as a service and Azure CosmosDB instead the MongoDB container (as both systems share the same access protocol). If you want to change to a production configuration, you'll just need to change the connection strings once you have set up the servers in an HA cloud or on-premises.
## Related documentation and guidance
While developing this reference application, we've been creating a reference <b>Guide/eBook</b> focusing on <b>architecting and developing containerized and microservice based .NET Applications</b> (download link available below) which explains in detail how to develop this kind of architectural style (microservices, Docker containers, Domain-Driven Design for certain microservices) plus other simpler architectural styles, like monolithic apps that can also live as Docker containers.
@ -76,12 +76,12 @@ Download in other formats (**eReaders** like **MOBI**, **EPUB**) and other eBook
Send feedback to [dotnet-architecture-ebooks-feedback@service.microsoft.com](dotnet-architecture-ebooks-feedback@service.microsoft.com)
However, we encourage to download and review the [Architecting and Developing Microservices eBook](https://aka.ms/microservicesebook) because the architectural styles and architectural patterns and technologies explained in the guidance are using this reference application when explaining many pattern implementations, so you'll understand much better the context, design and decisions taken in the current architecture and internal designs.
However, we encourage you to download and review the [Architecting and Developing Microservices eBook](https://aka.ms/microservicesebook) because the architectural styles and architectural patterns and technologies explained in the guide are using this reference application when explaining many pattern implementations, so you'll understand the context, design and decisions taken in the current architecture and internal designs much better.
## Overview of the application code
In this repo you can find a sample reference application that will help you to understand how to implement a microservice architecture based application using <b>.NET Core</b> and <b>Docker</b>.
The example business domain or scenario is based on an eShop or eCommerce which is implemented as a multi-container application. Each container is a microservice deployment (like the basket-microservice, catalog-microservice, ordering-microservice and the identity-microservice) which are developed using ASP.NET Core running on .NET Core so they can run either on Linux Containers and Windows Containers.
The example business domain or scenario is based on an eShop or eCommerce which is implemented as a multi-container application. Each container is a microservice deployment (like the basket-microservice, catalog-microservice, ordering-microservice and the identity-microservice) which is developed using ASP.NET Core running on .NET Core so they can run either on Linux Containers and Windows Containers.
The screenshot below shows the VS Solution structure for those microservices/containers and client apps.
- (*Recommended when getting started*) Open <b>eShopOnContainers-ServicesAndWebApps.sln</b> for a solution containing just the server-side projects related to the microservices and web applications.


BIN
docs/Decks/BRK3175_CesarDeIaTorre.pptx View File


BIN
docs/Decks/eShopOnContainers-Architecture-v2.1.pptx View File


+ 1
- 3
k8s/deploy-ingress-azure.ps1 View File

@ -1,3 +1 @@
kubectl patch deployment -n ingress-nginx nginx-ingress-controller --type=json --patch="$(cat nginx-ingress\publish-service-patch.yaml)"
kubectl apply -f nginx-ingress\azure\service.yaml
kubectl apply -f nginx-ingress\patch-service-without-rbac.yaml
kubectl apply -f nginx-ingress\cloud-generic.yaml

+ 2
- 0
k8s/deploy-ingress-dockerlocal.ps1 View File

@ -0,0 +1,2 @@
kubectl apply -f nginx-ingress\cm.yaml
kubectl apply -f nginx-ingress\cloud-generic.yaml

+ 1
- 8
k8s/deploy-ingress.ps1 View File

@ -1,12 +1,5 @@
kubectl apply -f ingress.yaml
# Deploy nginx-ingress core files
kubectl apply -f nginx-ingress\namespace.yaml
kubectl apply -f nginx-ingress\default-backend.yaml
kubectl apply -f nginx-ingress\configmap.yaml
kubectl apply -f nginx-ingress\tcp-services-configmap.yaml
kubectl apply -f nginx-ingress\udp-services-configmap.yaml
kubectl apply -f nginx-ingress\without-rbac.yaml
kubectl apply -f nginx-ingress\mandatory.yaml

+ 4
- 0
k8s/deploy.ps1 View File

@ -113,6 +113,7 @@ ExecKube -cmd 'delete configmap internalurls'
ExecKube -cmd 'delete configmap urls'
ExecKube -cmd 'delete configmap externalcfg'
ExecKube -cmd 'delete configmap ocelot'
ExecKube -cmd 'delete -f ingress.yaml'
# start sql, rabbitmq, frontend deployments
if ($deployInfrastructure) {
@ -204,5 +205,8 @@ ExecKube -cmd 'rollout resume deployments/apigwwm'
ExecKube -cmd 'rollout resume deployments/apigwws'
ExecKube -cmd 'rollout resume deployments/ordering-signalrhub'
Write-Host "Adding/Updating ingress resource..." -ForegroundColor Yellow
ExecKube -cmd 'apply -f ingress.yaml'
Write-Host "WebSPA is exposed at http://$externalDns, WebMVC at http://$externalDns/webmvc, WebStatus at http://$externalDns/webstatus" -ForegroundColor Yellow

+ 18
- 0
k8s/helm-rbac.yaml View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: tiller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tiller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: tiller
namespace: kube-system

+ 1
- 1
k8s/helm/app.yaml View File

@ -27,7 +27,7 @@ app: # app global settings
catalog: catalog # service name for catalog api
ordering: ordering # service name for ordering api
orderingbackgroundtasks: orderingbackgroundtasks # service name for orderingbackgroundtasks
orderingsignalrhub: orderingsignalrhub # service name for orderingsignalrhub
orderingsignalrhub: ordering-signalrhub # service name for orderingsignalrhub
identity: identity # service name for identity api
mvc: webmvc # service name for web mvc
spa: webspa # service name for web spa


+ 13
- 5
k8s/helm/deploy-all.ps1 View File

@ -8,11 +8,19 @@ Param(
[parameter(Mandatory=$false)][bool]$clean=$true,
[parameter(Mandatory=$false)][string]$aksName="",
[parameter(Mandatory=$false)][string]$aksRg="",
[parameter(Mandatory=$false)][string]$imageTag="latest"
)
[parameter(Mandatory=$false)][string]$imageTag="latest",
[parameter(Mandatory=$false)][bool]$useLocalk8s=$false
)
$dns = $externalDns
$ingressValuesFile="ingress_values.yaml"
if ($ingressValuesFile) {
$ingressValuesFile="ingress_values_dockerk8s.yaml"
$dns="localhost"
}
if ($externalDns -eq "aks") {
if ([string]::IsNullOrEmpty($aksName) -or [string]::IsNullOrEmpty($aksRg)) {
Write-Host "Error: When using -dns aks, MUST set -aksName and -aksRg too." -ForegroundColor Red
@ -58,18 +66,18 @@ $charts = ("eshop-common", "apigwmm", "apigwms", "apigwwm", "apigwws", "basket-a
if ($deployInfrastructure) {
foreach ($infra in $infras) {
Write-Host "Installing infrastructure: $infra" -ForegroundColor Green
helm install --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --name="$appName-$infra" $infra
helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --name="$appName-$infra" $infra
}
}
foreach ($chart in $charts) {
Write-Host "Installing: $chart" -ForegroundColor Green
if ($useCustomRegistry) {
helm install --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart
helm install --set inf.registry.server=$registry --set inf.registry.login=$dockerUser --set inf.registry.pwd=$dockerPassword --set inf.registry.secretName=eshop-docker-scret --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart
}
else {
if ($chart -ne "eshop-common") { # eshop-common is ignored when no secret must be deployed
helm install --values app.yaml --values inf.yaml --values ingress_values.yaml --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart
helm install --values app.yaml --values inf.yaml --values $ingressValuesFile --set app.name=$appName --set inf.k8s.dns=$dns --set image.tag=$imageTag --set image.pullPolicy=Always --name="$appName-$chart" $chart
}
}
}


+ 5
- 0
k8s/helm/ingress_values_dockerk8s.yaml View File

@ -0,0 +1,5 @@
ingress:
annotations:
kubernetes.io/ingress.class: "nginx"
ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/ssl-redirect: "false"

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

@ -15,4 +15,4 @@ data:
all__InstrumentationKey: {{ .Values.inf.appinsights.key }}
all__UseAzureServiceBus: "{{ .Values.inf.eventbus.useAzure }}"
signalr__StoreConnectionString: {{ .Values.inf.redis.keystore.constr }}
urls__IdentityUrl: {{ $identity }}
urls__IdentityUrl: http://{{ $identity }}

+ 0
- 19
k8s/nginx-ingress/azure/service.yaml View File

@ -1,19 +0,0 @@
kind: Service
apiVersion: v1
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app: ingress-nginx
spec:
externalTrafficPolicy: Local
type: LoadBalancer
selector:
app: ingress-nginx
ports:
- name: http
port: 80
targetPort: http
- name: https
port: 443
targetPort: https

+ 21
- 0
k8s/nginx-ingress/cloud-generic.yaml View File

@ -0,0 +1,21 @@
kind: Service
apiVersion: v1
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
externalTrafficPolicy: Local
type: LoadBalancer
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
ports:
- name: http
port: 80
targetPort: http
- name: https
port: 443
targetPort: https

BIN
k8s/nginx-ingress/cm.yaml View File


+ 0
- 11
k8s/nginx-ingress/configmap.yaml View File

@ -1,11 +0,0 @@
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app: ingress-nginx
data:
ssl-redirect: "false"
proxy-buffer-size: "128k"
proxy-buffers: "4 256k"

+ 0
- 52
k8s/nginx-ingress/default-backend.yaml View File

@ -1,52 +0,0 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: default-http-backend
labels:
app: default-http-backend
namespace: ingress-nginx
spec:
replicas: 1
template:
metadata:
labels:
app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
# Any image is permissable as long as:
# 1. It serves a 404 page at /
# 2. It serves 200 on a /healthz endpoint
image: gcr.io/google_containers/defaultbackend:1.4
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
---
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
namespace: ingress-nginx
labels:
app: default-http-backend
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: default-http-backend

+ 3
- 0
k8s/nginx-ingress/local-dockerk8s/identityapi-cm-fix.yaml View File

@ -0,0 +1,3 @@
data:
mvc_e: http://10.0.75.1/webmvc

+ 3
- 0
k8s/nginx-ingress/local-dockerk8s/mvc-cm-fix.yaml View File

@ -0,0 +1,3 @@
data:
urls__IdentityUrl: http://10.0.75.1/identity
urls__mvc: http://10.0.75.1/webmvc

+ 39
- 0
k8s/nginx-ingress/local-dockerk8s/mvc-fix.yaml View File

@ -0,0 +1,39 @@
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/ssl-redirect: "false"
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "false"
labels:
app: webmvc
name: eshop-webmvc-loopback
namespace: default
spec:
rules:
- http:
paths:
- backend:
serviceName: webmvc
servicePort: http
path: /webmvc
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/ssl-redirect: "false"
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "false"
labels:
app: identity-api
name: eshop-identity-api-loopback
namespace: default
spec:
rules:
- http:
paths:
- backend:
serviceName: identity
servicePort: http
path: /identity

+ 238
- 0
k8s/nginx-ingress/mandatory.yaml View File

@ -0,0 +1,238 @@
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: nginx-ingress-clusterrole
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-role
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
serviceAccountName: nginx-ingress-serviceaccount
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.20.0
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# www-data -> 33
runAsUser: 33
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1

+ 0
- 4
k8s/nginx-ingress/namespace.yaml View File

@ -1,4 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx

+ 0
- 40
k8s/nginx-ingress/patch-service-without-rbac.yaml View File

@ -1,40 +0,0 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app: ingress-nginx
template:
metadata:
labels:
app: ingress-nginx
spec:
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.9.0
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443

+ 0
- 7
k8s/nginx-ingress/publish-service-patch.yaml View File

@ -1,7 +0,0 @@
[
{
'op': 'add',
'path': '/spec/template/spec/containers/0/args/-',
'value': '--publish-service=$(POD_NAMESPACE)/ingress-nginx'
}
]

+ 22
- 0
k8s/nginx-ingress/service-nodeport.yaml View File

@ -0,0 +1,22 @@
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
- name: https
port: 443
targetPort: 443
protocol: TCP
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

+ 0
- 5
k8s/nginx-ingress/tcp-services-configmap.yaml View File

@ -1,5 +0,0 @@
kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx

+ 0
- 5
k8s/nginx-ingress/udp-services-configmap.yaml View File

@ -1,5 +0,0 @@
kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx

+ 0
- 61
k8s/nginx-ingress/without-rbac.yaml View File

@ -1,61 +0,0 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app: ingress-nginx
template:
metadata:
labels:
app: ingress-nginx
annotations:
prometheus.io/port: '10254'
prometheus.io/scrape: 'true'
spec:
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.9.0
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --annotations-prefix=nginx.ingress.kubernetes.io
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1

+ 1
- 1
src/ApiGateways/ApiGw-Base/OcelotApiGw.csproj View File

@ -10,6 +10,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Ocelot" Version="3.0.0" />
<PackageReference Include="Ocelot" Version="12.0.1" />
</ItemGroup>
</Project>

+ 1
- 7
src/ApiGateways/ApiGw-Base/Startup.cs View File

@ -1,11 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CacheManager.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;


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

@ -217,14 +217,16 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
if (subscription.IsDynamic)
{
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
if (handler == null) continue;
dynamic eventData = JObject.Parse(message);
await handler.Handle(eventData);
}
else
{
var handler = scope.ResolveOptional(subscription.HandlerType);
if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var handler = scope.ResolveOptional(subscription.HandlerType);
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}


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

@ -163,14 +163,16 @@
if (subscription.IsDynamic)
{
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
if (handler == null) continue;
dynamic eventData = JObject.Parse(message);
await handler.Handle(eventData);
}
else
{
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var handler = scope.ResolveOptional(subscription.HandlerType);
if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}


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

@ -7,7 +7,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
public enum EventStateEnum
{
NotPublished = 0,
Published = 1,
PublishedFailed = 2
InProgress = 1,
Published = 2,
PublishedFailed = 3
}
}

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

@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System.Linq;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
{
@ -11,7 +14,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
private IntegrationEventLogEntry() { }
public IntegrationEventLogEntry(IntegrationEvent @event)
{
EventId = @event.Id;
EventId = @event.Id;
CreationTime = @event.CreationDate;
EventTypeName = @event.GetType().FullName;
Content = JsonConvert.SerializeObject(@event);
@ -20,9 +23,19 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
}
public Guid EventId { get; private set; }
public string EventTypeName { get; private set; }
[NotMapped]
public string EventTypeShortName => EventTypeName.Split('.')?.Last();
[NotMapped]
public IntegrationEvent IntegrationEvent { get; private set; }
public EventStateEnum State { get; set; }
public int TimesSent { get; set; }
public DateTime CreationTime { get; private set; }
public string Content { get; private set; }
public IntegrationEventLogEntry DeserializeJsonContent(Type type)
{
IntegrationEvent = JsonConvert.DeserializeObject(Content, type) as IntegrationEvent;
return this;
}
}
}

+ 4
- 1
src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs View File

@ -9,7 +9,10 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi
{
public interface IIntegrationEventLogService
{
Task<IEnumerable<IntegrationEventLogEntry>> RetrieveEventLogsPendingToPublishAsync();
Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction);
Task MarkEventAsPublishedAsync(IntegrationEvent @event);
Task MarkEventAsPublishedAsync(Guid eventId);
Task MarkEventAsInProgressAsync(Guid eventId);
Task MarkEventAsFailedAsync(Guid eventId);
}
}

+ 41
- 4
src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs View File

@ -1,9 +1,14 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services
@ -12,6 +17,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi
{
private readonly IntegrationEventLogContext _integrationEventLogContext;
private readonly DbConnection _dbConnection;
private readonly List<Type> _eventTypes;
public IntegrationEventLogService(DbConnection dbConnection)
{
@ -21,6 +27,20 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi
.UseSqlServer(_dbConnection)
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
.Options);
_eventTypes = Assembly.Load(Assembly.GetEntryAssembly().FullName)
.GetTypes()
.Where(t => t.Name.EndsWith(nameof(IntegrationEvent)))
.ToList();
}
public async Task<IEnumerable<IntegrationEventLogEntry>> RetrieveEventLogsPendingToPublishAsync()
{
return await _integrationEventLogContext.IntegrationEventLogs
.Where(e => e.State == EventStateEnum.NotPublished)
.OrderBy(o => o.CreationTime)
.Select(e => e.DeserializeJsonContent(_eventTypes.Find(t=> t.Name == e.EventTypeShortName)))
.ToListAsync();
}
public Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction)
@ -38,11 +58,28 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Servi
return _integrationEventLogContext.SaveChangesAsync();
}
public Task MarkEventAsPublishedAsync(IntegrationEvent @event)
public Task MarkEventAsPublishedAsync(Guid eventId)
{
return UpdateEventStatus(eventId, EventStateEnum.Published);
}
public Task MarkEventAsInProgressAsync(Guid eventId)
{
var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == @event.Id);
eventLogEntry.TimesSent++;
eventLogEntry.State = EventStateEnum.Published;
return UpdateEventStatus(eventId, EventStateEnum.InProgress);
}
public Task MarkEventAsFailedAsync(Guid eventId)
{
return UpdateEventStatus(eventId, EventStateEnum.PublishedFailed);
}
private Task UpdateEventStatus(Guid eventId, EventStateEnum status)
{
var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == eventId);
eventLogEntry.State = status;
if(status == EventStateEnum.InProgress)
eventLogEntry.TimesSent++;
_integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry);


+ 0
- 2
src/Services/Basket/Basket.API/BasketSettings.cs View File

@ -3,7 +3,5 @@
public class BasketSettings
{
public string ConnectionString { get; set; }
public string EventBusConnection { get; set; }
}
}

+ 10
- 3
src/Services/Catalog/Catalog.API/IntegrationEvents/CatalogIntegrationEventService.cs View File

@ -29,9 +29,16 @@ namespace Catalog.API.IntegrationEvents
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
{
_eventBus.Publish(evt);
await _eventLogService.MarkEventAsPublishedAsync(evt);
try
{
await _eventLogService.MarkEventAsInProgressAsync(evt.Id);
_eventBus.Publish(evt);
await _eventLogService.MarkEventAsPublishedAsync(evt.Id);
}
catch (Exception)
{
await _eventLogService.MarkEventAsFailedAsync(evt.Id);
}
}
public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt)


+ 1
- 1
src/Services/Catalog/Catalog.API/Startup.cs View File

@ -232,7 +232,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API
public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
sp => (DbConnection c) => new IntegrationEventLogService(c));
sp => (DbConnection c) => new IntegrationEventLogService(c));
services.AddTransient<ICatalogIntegrationEventService, CatalogIntegrationEventService>();


+ 2
- 0
src/Services/Identity/Identity.API/Controllers/AccountController.cs View File

@ -216,6 +216,8 @@ namespace Microsoft.eShopOnContainers.Services.Identity.API.Controllers
// delete authentication cookie
await HttpContext.SignOutAsync();
await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
// set this so UI rendering sees an anonymous user
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());


+ 0
- 1
src/Services/Identity/Identity.API/Identity.API.csproj View File

@ -55,7 +55,6 @@
<ItemGroup>
<Folder Include="Extensions\" />
<Folder Include="wwwroot\lib\" />
</ItemGroup>
</Project>

+ 1
- 1
src/Services/Identity/Identity.API/Views/Shared/_Layout.cshtml View File

@ -18,7 +18,7 @@
</environment>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top es-header">
<div class="navbar navbar-inverse fixed-top es-header">
<div class="container">
<div class="row">
<div class="navbar-header col-sm-6 col-xs-8">


+ 2
- 16
src/Services/Identity/Identity.API/libman.json View File

@ -8,28 +8,14 @@
},
{
"provider": "unpkg",
"library": "bootstrap@3.3.7",
"library": "bootstrap@4.1.3",
"files": [
"dist/css/bootstrap.css",
"dist/css/bootstrap.css.map",
"dist/css/bootstrap.min.css",
"dist/css/bootstrap.min.css.map",
"dist/css/bootstrap-theme.css",
"dist/css/bootstrap-theme.css.map",
"dist/css/bootstrap-theme.min.css",
"dist/css/bootstrap-theme.min.css.map",
"dist/fonts/glyphicons-halflings-regular.eot",
"dist/fonts/glyphicons-halflings-regular.svg",
"dist/fonts/glyphicons-halflings-regular.ttf",
"dist/fonts/glyphicons-halflings-regular.woff",
"dist/fonts/glyphicons-halflings-regular.woff2",
"dist/js/bootstrap.js",
"dist/js/bootstrap.min.js",
"fonts/glyphicons-halflings-regular.eot",
"fonts/glyphicons-halflings-regular.svg",
"fonts/glyphicons-halflings-regular.ttf",
"fonts/glyphicons-halflings-regular.woff",
"fonts/glyphicons-halflings-regular.woff2"
"dist/js/bootstrap.min.js"
],
"destination": "wwwroot/lib/bootstrap/"
},


+ 62
- 0
src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs View File

@ -0,0 +1,62 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using Microsoft.Extensions.Logging;
using Ordering.API.Application.IntegrationEvents;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Ordering.API.Application.Behaviors
{
public class TransactionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<TransactionBehaviour<TRequest, TResponse>> _logger;
private readonly OrderingContext _dbContext;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public TransactionBehaviour(OrderingContext dbContext,
IOrderingIntegrationEventService orderingIntegrationEventService,
ILogger<TransactionBehaviour<TRequest, TResponse>> logger)
{
_dbContext = dbContext ?? throw new ArgumentException(nameof(OrderingContext));
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentException(nameof(orderingIntegrationEventService));
_logger = logger ?? throw new ArgumentException(nameof(ILogger));
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
TResponse response = default(TResponse);
try
{
var strategy = _dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
_logger.LogInformation($"Begin transaction {typeof(TRequest).Name}");
await _dbContext.BeginTransactionAsync();
response = await next();
await _dbContext.CommitTransactionAsync();
_logger.LogInformation($"Committed transaction {typeof(TRequest).Name}");
await _orderingIntegrationEventService.PublishEventsThroughEventBusAsync();
});
return response;
}
catch (Exception)
{
_logger.LogInformation($"Rollback transaction executed {typeof(TRequest).Name}");
_dbContext.RollbackTransaction();
throw;
}
}
}
}

+ 12
- 1
src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs View File

@ -1,6 +1,8 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
{
using Domain.AggregatesModel.OrderAggregate;
using global::Ordering.API.Application.IntegrationEvents;
using global::Ordering.API.Application.IntegrationEvents.Events;
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
@ -15,17 +17,26 @@
private readonly IOrderRepository _orderRepository;
private readonly IIdentityService _identityService;
private readonly IMediator _mediator;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
// Using DI to inject infrastructure persistence Repositories
public CreateOrderCommandHandler(IMediator mediator, IOrderRepository orderRepository, IIdentityService identityService)
public CreateOrderCommandHandler(IMediator mediator,
IOrderingIntegrationEventService orderingIntegrationEventService,
IOrderRepository orderRepository,
IIdentityService identityService)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
}
public async Task<bool> Handle(CreateOrderCommand message, CancellationToken cancellationToken)
{
// Add Integration event to clean the basket
var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(message.UserId);
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStartedIntegrationEvent);
// Add/Update the Buyer AggregateRoot
// DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root
// methods and constructor so validations, invariants and business logic


+ 21
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommand.cs View File

@ -0,0 +1,21 @@
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
public class SetAwaitingValidationOrderStatusCommand : IRequest<bool>
{
[DataMember]
public int OrderNumber { get; private set; }
public SetAwaitingValidationOrderStatusCommand(int orderNumber)
{
OrderNumber = orderNumber;
}
}
}

+ 52
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetAwaitingValidationOrderStatusCommandHandler.cs View File

@ -0,0 +1,52 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using System.Threading;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
// Regular CommandHandler
public class SetAwaitingValidationOrderStatusCommandHandler : IRequestHandler<SetAwaitingValidationOrderStatusCommand, bool>
{
private readonly IOrderRepository _orderRepository;
public SetAwaitingValidationOrderStatusCommandHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
/// <summary>
/// Handler which processes the command when
/// graceperiod has finished
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public async Task<bool> Handle(SetAwaitingValidationOrderStatusCommand command, CancellationToken cancellationToken)
{
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
if(orderToUpdate == null)
{
return false;
}
orderToUpdate.SetAwaitingValidationStatus();
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
// Use for Idempotency in Command process
public class SetAwaitingValidationIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler<SetAwaitingValidationOrderStatusCommand, bool>
{
public SetAwaitingValidationIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
}

+ 21
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommand.cs View File

@ -0,0 +1,21 @@
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
public class SetPaidOrderStatusCommand : IRequest<bool>
{
[DataMember]
public int OrderNumber { get; private set; }
public SetPaidOrderStatusCommand(int orderNumber)
{
OrderNumber = orderNumber;
}
}
}

+ 55
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetPaidOrderStatusCommandHandler.cs View File

@ -0,0 +1,55 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using System.Threading;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
// Regular CommandHandler
public class SetPaidOrderStatusCommandHandler : IRequestHandler<SetPaidOrderStatusCommand, bool>
{
private readonly IOrderRepository _orderRepository;
public SetPaidOrderStatusCommandHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
/// <summary>
/// Handler which processes the command when
/// Shipment service confirms the payment
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public async Task<bool> Handle(SetPaidOrderStatusCommand command, CancellationToken cancellationToken)
{
// Simulate a work time for validating the payment
await Task.Delay(10000);
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
if(orderToUpdate == null)
{
return false;
}
orderToUpdate.SetPaidStatus();
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
// Use for Idempotency in Command process
public class SetPaidIdentifiedOrderStatusCommandHandler : IdentifiedCommandHandler<SetPaidOrderStatusCommand, bool>
{
public SetPaidIdentifiedOrderStatusCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
}

+ 21
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommand.cs View File

@ -0,0 +1,21 @@
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
public class SetStockConfirmedOrderStatusCommand : IRequest<bool>
{
[DataMember]
public int OrderNumber { get; private set; }
public SetStockConfirmedOrderStatusCommand(int orderNumber)
{
OrderNumber = orderNumber;
}
}
}

+ 55
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetStockConfirmedOrderStatusCommandHandler.cs View File

@ -0,0 +1,55 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using System.Threading;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
// Regular CommandHandler
public class SetStockConfirmedOrderStatusCommandHandler : IRequestHandler<SetStockConfirmedOrderStatusCommand, bool>
{
private readonly IOrderRepository _orderRepository;
public SetStockConfirmedOrderStatusCommandHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
/// <summary>
/// Handler which processes the command when
/// Stock service confirms the request
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public async Task<bool> Handle(SetStockConfirmedOrderStatusCommand command, CancellationToken cancellationToken)
{
// Simulate a work time for confirming the stock
await Task.Delay(10000);
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
if(orderToUpdate == null)
{
return false;
}
orderToUpdate.SetStockConfirmedStatus();
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
// Use for Idempotency in Command process
public class SetStockConfirmedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler<SetStockConfirmedOrderStatusCommand, bool>
{
public SetStockConfirmedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
}

+ 25
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommand.cs View File

@ -0,0 +1,25 @@
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
public class SetStockRejectedOrderStatusCommand : IRequest<bool>
{
[DataMember]
public int OrderNumber { get; private set; }
[DataMember]
public List<int> OrderStockItems { get; private set; }
public SetStockRejectedOrderStatusCommand(int orderNumber, List<int> orderStockItems)
{
OrderNumber = orderNumber;
OrderStockItems = orderStockItems;
}
}
}

+ 56
- 0
src/Services/Ordering/Ordering.API/Application/Commands/SetStockRejectedOrderStatusCommandHandler.cs View File

@ -0,0 +1,56 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
using System.Threading;
using System.Threading.Tasks;
namespace Ordering.API.Application.Commands
{
// Regular CommandHandler
public class SetStockRejectedOrderStatusCommandHandler : IRequestHandler<SetStockRejectedOrderStatusCommand, bool>
{
private readonly IOrderRepository _orderRepository;
public SetStockRejectedOrderStatusCommandHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
/// <summary>
/// Handler which processes the command when
/// Stock service rejects the request
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
public async Task<bool> Handle(SetStockRejectedOrderStatusCommand command, CancellationToken cancellationToken)
{
// Simulate a work time for rejecting the stock
await Task.Delay(10000);
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
if(orderToUpdate == null)
{
return false;
}
orderToUpdate.SetCancelledStatusWhenStockIsRejected(command.OrderStockItems);
return await _orderRepository.UnitOfWork.SaveEntitiesAsync();
}
}
// Use for Idempotency in Command process
public class SetStockRejectedOrderStatusIdenfifiedCommandHandler : IdentifiedCommandHandler<SetStockRejectedOrderStatusCommand, bool>
{
public SetStockRejectedOrderStatusIdenfifiedCommandHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for processing order.
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderCancelled/OrderCancelledDomainEventHandler.cs View File

@ -41,7 +41,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderCancelled
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
var orderStatusChangedToCancelledIntegrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToCancelledIntegrationEvent);
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToCancelledIntegrationEvent);
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderGracePeriodConfirmed/OrderStatusChangedToAwaitingValidationDomainEventHandler.cs View File

@ -46,7 +46,7 @@
var orderStatusChangedToAwaitingValidationIntegrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent(
order.Id, order.OrderStatus.Name, buyer.Name, orderStockList);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToAwaitingValidationIntegrationEvent);
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToAwaitingValidationIntegrationEvent);
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderPaid/OrderStatusChangedToPaidDomainEventHandler.cs View File

@ -51,7 +51,7 @@
buyer.Name,
orderStockList);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToPaidIntegrationEvent);
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToPaidIntegrationEvent);
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderShipped/OrderShippedDomainEventHandler.cs View File

@ -41,7 +41,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderShipped
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
var orderStatusChangedToShippedIntegrationEvent = new OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToShippedIntegrationEvent);
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToShippedIntegrationEvent);
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStartedEvent/ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler.cs View File

@ -59,7 +59,7 @@ namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent
.SaveEntitiesAsync();
var orderStatusChangedTosubmittedIntegrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(orderStartedEvent.Order.Id, orderStartedEvent.Order.OrderStatus.Name, buyer.Name);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedTosubmittedIntegrationEvent);
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedTosubmittedIntegrationEvent);
_logger.CreateLogger(nameof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler)).LogTrace($"Buyer {buyerUpdated.Id} and related payment method were validated or updated for orderId: {orderStartedEvent.Order.Id}.");
}


+ 1
- 1
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmed/OrderStatusChangedToStockConfirmedDomainEventHandler.cs View File

@ -41,7 +41,7 @@
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value.ToString());
var orderStatusChangedToStockConfirmedIntegrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus.Name, buyer.Name);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStatusChangedToStockConfirmedIntegrationEvent);
await _orderingIntegrationEventService.AddAndSaveEventAsync(orderStatusChangedToStockConfirmedIntegrationEvent);
}
}
}

+ 8
- 7
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/GracePeriodConfirmedIntegrationEventHandler.cs View File

@ -1,5 +1,7 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using MediatR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.Commands;
using Ordering.API.Application.IntegrationEvents.Events;
using System.Threading.Tasks;
@ -7,11 +9,11 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
public class GracePeriodConfirmedIntegrationEventHandler : IIntegrationEventHandler<GracePeriodConfirmedIntegrationEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly IMediator _mediator;
public GracePeriodConfirmedIntegrationEventHandler(IOrderRepository orderRepository)
public GracePeriodConfirmedIntegrationEventHandler(IMediator mediator)
{
_orderRepository = orderRepository;
_mediator = mediator;
}
/// <summary>
@ -24,9 +26,8 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
/// <returns></returns>
public async Task Handle(GracePeriodConfirmedIntegrationEvent @event)
{
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
orderToUpdate.SetAwaitingValidationStatus();
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
var command = new SetAwaitingValidationOrderStatusCommand(@event.OrderId);
await _mediator.Send(command);
}
}
}

+ 8
- 8
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentFailedIntegrationEventHandler.cs View File

@ -1,27 +1,27 @@
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
using MediatR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.Commands;
using Ordering.API.Application.IntegrationEvents.Events;
using System;
using System.Threading.Tasks;
public class OrderPaymentFailedIntegrationEventHandler :
IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly IMediator _mediator;
public OrderPaymentFailedIntegrationEventHandler(IOrderRepository orderRepository)
public OrderPaymentFailedIntegrationEventHandler(IMediator mediator)
{
_orderRepository = orderRepository;
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
}
public async Task Handle(OrderPaymentFailedIntegrationEvent @event)
{
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
orderToUpdate.SetCancelledStatus();
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
var command = new CancelOrderCommand(@event.OrderId);
await _mediator.Send(command);
}
}
}

+ 8
- 11
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderPaymentSuccededIntegrationEventHandler.cs View File

@ -1,30 +1,27 @@
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
using MediatR;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.Commands;
using Ordering.API.Application.IntegrationEvents.Events;
using System;
using System.Threading.Tasks;
public class OrderPaymentSuccededIntegrationEventHandler :
IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly IMediator _mediator;
public OrderPaymentSuccededIntegrationEventHandler(IOrderRepository orderRepository)
public OrderPaymentSuccededIntegrationEventHandler(IMediator mediator)
{
_orderRepository = orderRepository;
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
}
public async Task Handle(OrderPaymentSuccededIntegrationEvent @event)
{
// Simulate a work time for validating the payment
await Task.Delay(10000);
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
orderToUpdate.SetPaidStatus();
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
var command = new SetPaidOrderStatusCommand(@event.OrderId);
await _mediator.Send(command);
}
}
}

+ 8
- 11
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs View File

@ -4,27 +4,24 @@
using System.Threading.Tasks;
using Events;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using MediatR;
using System;
using Ordering.API.Application.Commands;
public class OrderStockConfirmedIntegrationEventHandler :
IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly IMediator _mediator;
public OrderStockConfirmedIntegrationEventHandler(IOrderRepository orderRepository)
public OrderStockConfirmedIntegrationEventHandler(IMediator mediator)
{
_orderRepository = orderRepository;
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
}
public async Task Handle(OrderStockConfirmedIntegrationEvent @event)
{
// Simulate a work time for confirming the stock
await Task.Delay(10000);
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
orderToUpdate.SetStockConfirmedStatus();
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
var command = new SetStockConfirmedOrderStatusCommand(@event.OrderId);
await _mediator.Send(command);
}
}
}

+ 9
- 9
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockRejectedIntegrationEventHandler.cs View File

@ -5,27 +5,27 @@
using Events;
using System.Linq;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using MediatR;
using Ordering.API.Application.Commands;
public class OrderStockRejectedIntegrationEventHandler : IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly IMediator _mediator;
public OrderStockRejectedIntegrationEventHandler(IOrderRepository orderRepository)
public OrderStockRejectedIntegrationEventHandler(IMediator mediator)
{
_orderRepository = orderRepository;
_mediator = mediator;
}
public async Task Handle(OrderStockRejectedIntegrationEvent @event)
{
var orderToUpdate = await _orderRepository.GetAsync(@event.OrderId);
var orderStockRejectedItems = @event.OrderStockItems
.FindAll(c => !c.HasStock)
.Select(c => c.ProductId);
orderToUpdate.SetCancelledStatusWhenStockIsRejected(orderStockRejectedItems);
.Select(c => c.ProductId)
.ToList();
await _orderRepository.UnitOfWork.SaveEntitiesAsync();
var command = new SetStockRejectedOrderStatusCommand(@event.OrderId, orderStockRejectedItems);
await _mediator.Send(command);
}
}
}

+ 4
- 10
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/UserCheckoutAcceptedIntegrationEventHandler.cs View File

@ -11,15 +11,13 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
public class UserCheckoutAcceptedIntegrationEventHandler : IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>
{
private readonly IMediator _mediator;
private readonly ILoggerFactory _logger;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
private readonly ILoggerFactory _logger;
public UserCheckoutAcceptedIntegrationEventHandler(IMediator mediator,
ILoggerFactory logger, IOrderingIntegrationEventService orderingIntegrationEventService)
ILoggerFactory logger)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
@ -34,11 +32,7 @@ namespace Ordering.API.Application.IntegrationEvents.EventHandling
public async Task Handle(UserCheckoutAcceptedIntegrationEvent eventMsg)
{
var result = false;
// Send Integration event to clean basket once basket is converted to Order and before starting with the order creation process
var orderStartedIntegrationEvent = new OrderStartedIntegrationEvent(eventMsg.UserId);
await _orderingIntegrationEventService.PublishThroughEventBusAsync(orderStartedIntegrationEvent);
if (eventMsg.RequestId != Guid.Empty)
{
var createOrderCommand = new CreateOrderCommand(eventMsg.Basket.Items, eventMsg.UserId, eventMsg.UserName, eventMsg.City, eventMsg.Street,


+ 2
- 1
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/IOrderingIntegrationEventService.cs View File

@ -5,6 +5,7 @@ namespace Ordering.API.Application.IntegrationEvents
{
public interface IOrderingIntegrationEventService
{
Task PublishThroughEventBusAsync(IntegrationEvent evt);
Task PublishEventsThroughEventBusAsync();
Task AddAndSaveEventAsync(IntegrationEvent evt);
}
}

+ 25
- 15
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/OrderingIntegrationEventService.cs View File

@ -2,12 +2,14 @@
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using System;
using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Ordering.API.Application.IntegrationEvents
@ -17,34 +19,42 @@ namespace Ordering.API.Application.IntegrationEvents
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
private readonly IEventBus _eventBus;
private readonly OrderingContext _orderingContext;
private readonly IntegrationEventLogContext _eventLogContext;
private readonly IIntegrationEventLogService _eventLogService;
public OrderingIntegrationEventService(IEventBus eventBus, OrderingContext orderingContext,
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
public OrderingIntegrationEventService(IEventBus eventBus,
OrderingContext orderingContext,
IntegrationEventLogContext eventLogContext,
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
{
_orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext));
_eventLogContext = eventLogContext ?? throw new ArgumentNullException(nameof(eventLogContext));
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory));
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
_eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection());
}
public async Task PublishThroughEventBusAsync(IntegrationEvent evt)
public async Task PublishEventsThroughEventBusAsync()
{
await SaveEventAndOrderingContextChangesAsync(evt);
_eventBus.Publish(evt);
await _eventLogService.MarkEventAsPublishedAsync(evt);
var pendindLogEvents = await _eventLogService.RetrieveEventLogsPendingToPublishAsync();
foreach (var logEvt in pendindLogEvents)
{
try
{
await _eventLogService.MarkEventAsInProgressAsync(logEvt.EventId);
_eventBus.Publish(logEvt.IntegrationEvent);
await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId);
}
catch (Exception)
{
await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId);
}
}
}
private async Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt)
public async Task AddAndSaveEventAsync(IntegrationEvent evt)
{
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
await ResilientTransaction.New(_orderingContext)
.ExecuteAsync(async () => {
// Achieving atomicity between original ordering database operation and the IntegrationEventLog thanks to a local transaction
await _orderingContext.SaveChangesAsync();
await _eventLogService.SaveEventAsync(evt, _orderingContext.Database.CurrentTransaction.GetDbTransaction());
});
await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction.GetDbTransaction());
}
}
}

+ 3
- 0
src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs View File

@ -4,6 +4,7 @@ using Autofac;
using FluentValidation;
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Ordering.API.Application.Behaviors;
using Ordering.API.Application.DomainEventHandlers.OrderStartedEvent;
using Ordering.API.Application.Validations;
using Ordering.API.Infrastructure.Behaviors;
@ -40,6 +41,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
builder.RegisterGeneric(typeof(LoggingBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.RegisterGeneric(typeof(ValidatorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.RegisterGeneric(typeof(TransactionBehaviour<,>)).As(typeof(IPipelineBehavior<,>));
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Infrastructure/Middlewares/ByPassAuthMiddleware.cs View File

@ -62,7 +62,7 @@ namespace Ordering.API.Infrastructure.Middlewares
new Claim("emails", currentUserId),
new Claim("name", "Test user"),
new Claim("nonce", Guid.NewGuid().ToString()),
new Claim("ttp://schemas.microsoft.com/identity/claims/identityprovider", "ByPassAuthMiddleware"),
new Claim("http://schemas.microsoft.com/identity/claims/identityprovider", "ByPassAuthMiddleware"),
new Claim("nonce", Guid.NewGuid().ToString()),
new Claim("sub", "1234"),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname","User"),


+ 1
- 1
src/Services/Ordering/Ordering.API/Startup.cs View File

@ -113,7 +113,7 @@
eventBus.Subscribe<OrderStockConfirmedIntegrationEvent, IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>>();
eventBus.Subscribe<OrderStockRejectedIntegrationEvent, IIntegrationEventHandler<OrderStockRejectedIntegrationEvent>>();
eventBus.Subscribe<OrderPaymentFailedIntegrationEvent, IIntegrationEventHandler<OrderPaymentFailedIntegrationEvent>>();
eventBus.Subscribe<OrderPaymentSuccededIntegrationEvent, IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>>();
eventBus.Subscribe<OrderPaymentSuccededIntegrationEvent, IIntegrationEventHandler<OrderPaymentSuccededIntegrationEvent>>();
}


+ 5
- 5
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Address.cs View File

@ -6,11 +6,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
{
public class Address : ValueObject
{
public String Street { get; }
public String City { get; }
public String State { get; }
public String Country { get; }
public String ZipCode { get; }
public String Street { get; private set; }
public String City { get; private set; }
public String State { get; private set; }
public String Country { get; private set; }
public String ZipCode { get; private set; }
private Address() { }


+ 49
- 1
src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs View File

@ -1,12 +1,14 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using Ordering.Infrastructure;
using Ordering.Infrastructure.EntityConfigurations;
using System;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
@ -23,9 +25,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
public DbSet<OrderStatus> OrderStatus { get; set; }
private readonly IMediator _mediator;
private IDbContextTransaction _currentTransaction;
private OrderingContext(DbContextOptions<OrderingContext> options) : base (options) { }
public IDbContextTransaction GetCurrentTransaction => _currentTransaction;
public OrderingContext(DbContextOptions<OrderingContext> options, IMediator mediator) : base(options)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
@ -60,7 +65,50 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
var result = await base.SaveChangesAsync();
return true;
}
}
public async Task BeginTransactionAsync()
{
_currentTransaction = _currentTransaction ?? await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted);
}
public async Task CommitTransactionAsync()
{
try
{
await SaveChangesAsync();
_currentTransaction?.Commit();
}
catch
{
RollbackTransaction();
throw;
}
finally
{
if (_currentTransaction != null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
public void RollbackTransaction()
{
try
{
_currentTransaction?.Rollback();
}
finally
{
if (_currentTransaction != null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
}
public class OrderingContextDesignFactory : IDesignTimeDbContextFactory<OrderingContext>


+ 4
- 1
src/Services/Ordering/Ordering.UnitTests/Application/NewOrderCommandHandlerTest.cs View File

@ -10,6 +10,7 @@ using System.Threading.Tasks;
namespace UnitTest.Ordering.Application
{
using global::Ordering.API.Application.IntegrationEvents;
using global::Ordering.API.Application.Models;
using MediatR;
using System.Collections;
@ -22,12 +23,14 @@ namespace UnitTest.Ordering.Application
private readonly Mock<IOrderRepository> _orderRepositoryMock;
private readonly Mock<IIdentityService> _identityServiceMock;
private readonly Mock<IMediator> _mediator;
private readonly Mock<IOrderingIntegrationEventService> _orderingIntegrationEventService;
public NewOrderRequestHandlerTest()
{
_orderRepositoryMock = new Mock<IOrderRepository>();
_identityServiceMock = new Mock<IIdentityService>();
_orderingIntegrationEventService = new Mock<IOrderingIntegrationEventService>();
_mediator = new Mock<IMediator>();
}
@ -48,7 +51,7 @@ namespace UnitTest.Ordering.Application
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId);
//Act
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderingIntegrationEventService.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
var cltToken = new System.Threading.CancellationToken();
var result = await handler.Handle(fakeOrderCmd, cltToken);


+ 7
- 7
src/Web/WebMVC/Views/Campaigns/Details.cshtml View File

@ -15,14 +15,14 @@
<div class="container">
<div class="card esh-campaigns-items">
<img class="card-img-top" src="@Model.PictureUri" alt="Card image cap">
<div class="card-block">
<div class="card-body">
<h4 class="card-title">@Model.Name</h4>
<p class="card-text">@Model.Description</p>
<p class="card-text">
<small class="text-muted">
From @Model.From.ToString("MMMM dd, yyyy") until @Model.To.ToString("MMMM dd, yyyy")
</small>
</p>
<p class="card-text">@Model.Description</p>
</div>
<div class="card-footer">
<small class="text-muted">
From @Model.From.ToString("MMMM dd, yyyy") until @Model.To.ToString("MMMM dd, yyyy")
</small>
</div>
</div>
</div>

+ 20
- 16
src/Web/WebMVC/Views/Campaigns/Index.cshtml View File

@ -27,24 +27,28 @@
<div class="esh-campaigns-items" style="font-weight: 300;">
UPDATE USER LOCATION
</div>
<br />
<form class="form-inline" asp-action="CreateNewUserLocation" method="post">
<label class="sr-only" for="longitudeInput">Name</label>
<form class="form-inline" asp-action="CreateNewUserLocation" method="post">
<label class="sr-only" for="longitudeInput">Name</label>
<div class="input-group mb-2 mr-sm-2 mb-sm-0">
<div class="input-group-addon">Lat</div>
<input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" id="latitudeInput" asp-for="Lat" placeholder="Latitude">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default">Lat</span>
</div>
<input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" id="latitudeInput" asp-for="Lat" placeholder="Latitude">
</div>
<div class="input-group mb-2 mr-sm-2 mb-sm-0">
<div class="input-group-addon">Lon</div>
<input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" id="longitudeInput" asp-for="Lon" placeholder="Longitude">
</div>
<div class="input-group mb-2 mr-sm-2 mb-sm-0 col-md-2">
<input type="submit" value="Update" class="btn esh-campaigns-form-button" />
</div>
</form>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="inputGroup-sizing-default">Lon</span>
</div>
<input type="text" class="form-control mb-2 mr-sm-2 mb-sm-0" id="longitudeInput" asp-for="Lon" placeholder="Longitude">
</div>
<div class="input-group mb-2 mr-sm-2 mb-sm-0 col-md-2">
<input type="submit" value="Update" class="btn esh-campaigns-form-button" />
</div>
</form>
</div>
</div>
<br />


+ 8
- 6
src/Web/WebMVC/Views/Campaigns/_campaign.cshtml View File

@ -1,9 +1,10 @@
@model CampaignItem
<form asp-controller="Campaigns" asp-action="Details" asp-route-id="@Model.Id">
<div class="card-block">
<form asp-controller="Campaigns" asp-action="Details" asp-route-id="@Model.Id">
<div class="card">
<img class="card-img-top esh-campaigns-thumbnail" src="@Model.PictureUri" alt="@Model.Name">
<div class="card-body">
<h4 class="card-title esh-campaigns-name">@Model.Name</h4>
<img class="card-img-top esh-campaigns-thumbnail" src="@Model.PictureUri" alt="@Model.Name">
@if (ViewBag.IsCampaignDetailFunctionActive == true)
{
<input type="button" value="More Details" class="btn esh-campaigns-button" onClick="window.open('@Model.DetailsUri')">
@ -11,11 +12,12 @@
else
{
<input class="esh-campaigns-button" type="submit" value="More details">
}
}
</div>
<div class="card-footer">
<div class="card-footer esh-campaigns-card-footer-text">
<small class="text-muted">
From @Model.From.ToString("MMMM dd, yyyy") until @Model.To.ToString("MMMM dd, yyyy")
</small>
</div>
</form>
</div>
</form>

+ 1
- 1
src/Web/WebMVC/Views/Order/Create.cshtml View File

@ -87,7 +87,7 @@
@await Html.PartialAsync("_OrderItems")
<section class="esh-orders_new-section">
<div class="form-group">
<div class="form-group row">
<div class="col-md-9">
</div>
<div class="col-md-2">


+ 23
- 23
src/Web/WebMVC/Views/Order/Detail.cshtml View File

@ -13,51 +13,51 @@
<div class="container">
<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-3">Order number</section>
<section class="esh-orders_detail-title col-xs-3">Date</section>
<section class="esh-orders_detail-title col-xs-3">Total</section>
<section class="esh-orders_detail-title col-xs-3">Status</section>
<section class="esh-orders_detail-title col-3">Order number</section>
<section class="esh-orders_detail-title col-3">Date</section>
<section class="esh-orders_detail-title col-3">Total</section>
<section class="esh-orders_detail-title col-3">Status</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-3">@Model.OrderNumber</section>
<section class="esh-orders_detail-item col-xs-3">@Model.Date</section>
<section class="esh-orders_detail-item col-xs-3">$@Model.Total</section>
<section class="esh-orders_detail-title col-xs-3">@Model.Status</section>
<section class="esh-orders_detail-item col-3">@Model.OrderNumber</section>
<section class="esh-orders_detail-item col-3">@Model.Date</section>
<section class="esh-orders_detail-item col-3">$@Model.Total</section>
<section class="esh-orders_detail-title col-3">@Model.Status</section>
</article>
</section>
<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-12">Description</section>
<section class="esh-orders_detail-title col-12">Description</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.Description</section>
<section class="esh-orders_detail-item col-12">@Model.Description</section>
</article>
</section>
<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-12">Shiping address</section>
<section class="esh-orders_detail-title col-12">Shiping address</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.Street</section>
<section class="esh-orders_detail-item col-12">@Model.Street</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.City</section>
<section class="esh-orders_detail-item col-12">@Model.City</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-12">@Model.Country</section>
<section class="esh-orders_detail-item col-12">@Model.Country</section>
</article>
</section>
<section class="esh-orders_detail-section">
<article class="esh-orders_detail-titles row">
<section class="esh-orders_detail-title col-xs-12">ORDER DETAILS</section>
<section class="esh-orders_detail-title col-12">ORDER DETAILS</section>
</article>
@for (int i = 0; i < Model.OrderItems.Count; i++)
@ -67,23 +67,23 @@
<section class="esh-orders_detail-item col-md-4 hidden-md-down">
<img class="esh-orders_detail-image" src="@item.PictureUrl">
</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-4">@item.ProductName</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-1">@item.Units</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-xs-2">$ @Math.Round(item.Units * item.UnitPrice, 2).ToString("N2")</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-4">@item.ProductName</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-1">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-1">@item.Units</section>
<section class="esh-orders_detail-item esh-orders_detail-item--middle col-2">$ @Math.Round(item.Units * item.UnitPrice, 2).ToString("N2")</section>
</article>
}
</section>
<section class="esh-orders_detail-section esh-orders_detail-section--right">
<article class="esh-orders_detail-titles esh-basket-titles--clean row">
<section class="esh-orders_detail-title col-xs-9"></section>
<section class="esh-orders_detail-title col-xs-2">TOTAL</section>
<section class="esh-orders_detail-title col-9"></section>
<section class="esh-orders_detail-title col-2">TOTAL</section>
</article>
<article class="esh-orders_detail-items row">
<section class="esh-orders_detail-item col-xs-9"></section>
<section class="esh-orders_detail-item esh-orders_detail-item--mark col-xs-2">$ @Model.Total</section>
<section class="esh-orders_detail-item col-9"></section>
<section class="esh-orders_detail-item esh-orders_detail-item--mark col-2">$ @Model.Total</section>
</article>
</section>
</div>


+ 11
- 11
src/Web/WebMVC/Views/Order/Index.cshtml View File

@ -14,25 +14,25 @@ new Header() { Controller = "OrderManagement", Text = "Orders Management" } })
<div class="container">
<article class="esh-orders-titles row">
<section class="esh-orders-title col-xs-2">Order number</section>
<section class="esh-orders-title col-xs-4">Date</section>
<section class="esh-orders-title col-xs-2">Total</section>
<section class="esh-orders-title col-xs-2">Status</section>
<section class="esh-orders-title col-xs-2"></section>
<section class="esh-orders-title col-2">Order number</section>
<section class="esh-orders-title col-4">Date</section>
<section class="esh-orders-title col-2">Total</section>
<section class="esh-orders-title col-2">Status</section>
<section class="esh-orders-title col-2"></section>
</article>
@if (Model != null && Model.Any())
{
foreach (var item in Model)
{
<article class="esh-orders-items row">
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section>
<section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.Date)</section>
<section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section>
<section class="esh-orders-item col-xs-1">
<section class="esh-orders-item col-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section>
<section class="esh-orders-item col-4">@Html.DisplayFor(modelItem => item.Date)</section>
<section class="esh-orders-item col-2">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-2">@Html.DisplayFor(modelItem => item.Status)</section>
<section class="esh-orders-item col-1">
<a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a>
</section>
<section class="esh-orders-item col-xs-1">
<section class="esh-orders-item col-1">
@if (item.Status.ToLower() == "submitted")
{
<a class="esh-orders-link" asp-controller="Order" asp-action="cancel" asp-route-orderId="@item.OrderNumber">Cancel</a>


+ 9
- 9
src/Web/WebMVC/Views/Order/_OrderItems.cshtml View File

@ -3,7 +3,7 @@
<section class="esh-orders_new-section">
<article class="esh-orders_new-titles row">
<section class="esh-orders_new-title col-xs-12">Order details</section>
<section class="esh-orders_new-title col-12">Order details</section>
</article>
@for (int i = 0; i < Model.OrderItems.Count; i++)
@ -15,32 +15,32 @@
<img class="esh-orders_new-image" src="@item.PictureUrl">
<input type="hidden" value="@item.PictureUrl" name=@("orderitems[" + i + "].PictureUrl") />
</section>
<section class="esh-orders_new-item esh-orders_new-item--middle col-xs-4">
<section class="esh-orders_new-item esh-orders_new-item--middle col-4">
@item.ProductName
<input type="hidden" value="@item.ProductName" name=@("orderitems[" + i + "].ProductName") />
</section>
<section class="esh-orders_new-item esh-orders_new-item--middle col-xs-1">
<section class="esh-orders_new-item esh-orders_new-item--middle col-1">
$ @item.UnitPrice.ToString("N2")
<input type="hidden" value="@item.UnitPrice" name=@("orderitems[" + i + "].UnitPrice") />
</section>
<section class="esh-orders_new-item esh-orders_new-item--middle col-xs-1">
<section class="esh-orders_new-item esh-orders_new-item--middle col-1">
@item.Units
<input type="hidden" value="@item.Units" name=@("orderitems[" + i + "].Units") />
</section>
<section class="esh-orders_new-item esh-orders_new-item--middle col-xs-2">$ @Math.Round(item.Units * item.UnitPrice, 2).ToString("N2")</section>
<section class="esh-orders_new-item esh-orders_new-item--middle col-2">$ @Math.Round(item.Units * item.UnitPrice, 2).ToString("N2")</section>
</article>
}
</section>
<section class="esh-orders_new-section esh-orders_new-section--right">
<article class="esh-orders_new-titles row">
<section class="esh-orders_new-title col-xs-9"></section>
<section class="esh-orders_new-title col-xs-2">Total</section>
<section class="esh-orders_new-title col-9"></section>
<section class="esh-orders_new-title col-2">Total</section>
</article>
<article class="esh-orders_new-items row">
<section class="esh-orders_new-item col-xs-9"></section>
<section class="esh-orders_new-item esh-orders_new-item--mark col-xs-2">
<section class="esh-orders_new-item col-9"></section>
<section class="esh-orders_new-item esh-orders_new-item--mark col-2">
$ @Model.Total.ToString("N2")
<input type="hidden" value="@Model.Total" name="Total"/>
</section>


+ 10
- 10
src/Web/WebMVC/Views/OrderManagement/Index.cshtml View File

@ -12,21 +12,21 @@
<div class="container">
<article class="esh-orders-titles row">
<section class="esh-orders-title col-xs-2">Order number</section>
<section class="esh-orders-title col-xs-4">Date</section>
<section class="esh-orders-title col-xs-2">Total</section>
<section class="esh-orders-title col-xs-2">Status</section>
<section class="esh-orders-title col-xs-2"></section>
<section class="esh-orders-title col-2">Order number</section>
<section class="esh-orders-title col-4">Date</section>
<section class="esh-orders-title col-2">Total</section>
<section class="esh-orders-title col-2">Status</section>
<section class="esh-orders-title col-2"></section>
</article>
@foreach (var item in Model)
{
<article class="esh-orders-items row">
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section>
<section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.Date)</section>
<section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section>
<section class="esh-orders-item col-xs-2">
<section class="esh-orders-item col-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section>
<section class="esh-orders-item col-4">@Html.DisplayFor(modelItem => item.Date)</section>
<section class="esh-orders-item col-2">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-2">@Html.DisplayFor(modelItem => item.Status)</section>
<section class="esh-orders-item col-2">
<form asp-action="OrderProcess" id="orderForm+@item.OrderNumber" method="post">
<input type="hidden" name="orderId" value="@item.OrderNumber" />
<select name="actionCode" asp-items="@item.ActionCodeSelectList"


+ 24
- 29
src/Web/WebMVC/Views/Shared/Components/CartList/Default.cshtml View File

@ -23,34 +23,29 @@
</div>
}
<section class="esh-basket-title col-xs-3">Product</section>
<section class="esh-basket-title col-xs-3 hidden-lg-down"></section>
<section class="esh-basket-title col-xs-2">Price</section>
<section class="esh-basket-title col-xs-2">Quantity</section>
<section class="esh-basket-title col-xs-2">Cost</section>
<section class="esh-basket-title col-3">Product</section>
<section class="esh-basket-title col-3 hidden-lg-down"></section>
<section class="esh-basket-title col-2">Price</section>
<section class="esh-basket-title col-2">Quantity</section>
<section class="esh-basket-title col-2">Cost</section>
</article>
@for (int i = 0; i < Model.Items.Count; i++)
{
var item = Model.Items[i];
<article class="esh-basket-items row">
<div>
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
<img class="esh-basket-image" src="@item.PictureUrl" />
</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-3">@item.ProductName</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2">
<input type="hidden" name="@("quantities[" + i +"].Key")" value="@item.Id" />
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + i +"].Value")" value="@item.Quantity" />
</section>
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2">$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString("N2")</section>
</div>
<div class="row">
</div>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
<img class="esh-basket-image" src="@item.PictureUrl" />
</section>
<section class="esh-basket-item esh-basket-item--middle col-3">@item.ProductName</section>
<section class="esh-basket-item esh-basket-item--middle col-2">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-basket-item esh-basket-item--middle col-2">
<input type="hidden" name="@("quantities[" + i +"].Key")" value="@item.Id" />
<input type="number" class="esh-basket-input" min="1" name="@("quantities[" + i +"].Value")" value="@item.Quantity" />
</section>
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-2">$ @Math.Round(item.Quantity * item.UnitPrice, 2).ToString("N2")</section>
</article>
<div class="esh-basket-items--border row">
@if (item.OldUnitPrice != 0)
@ -63,21 +58,21 @@
<div class="container">
<article class="esh-basket-titles esh-basket-titles--clean row">
<section class="esh-basket-title col-xs-10"></section>
<section class="esh-basket-title col-xs-2">Total</section>
<section class="esh-basket-title col-10"></section>
<section class="esh-basket-title col-2">Total</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-10"></section>
<section class="esh-basket-item esh-basket-item--mark col-xs-2">$ @Model.Total()</section>
<section class="esh-basket-item col-10"></section>
<section class="esh-basket-item esh-basket-item--mark col-2">$ @Model.Total()</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-7"></section>
<section class="esh-basket-item col-xs-2">
<section class="esh-basket-item col-7"></section>
<section class="esh-basket-item col-2">
<button class="btn esh-basket-checkout" name="name" value="" type="submit">[ Update ]</button>
</section>
<section class="esh-basket-item col-xs-3">
<section class="esh-basket-item col-3">
<input type="submit"
class="btn esh-basket-checkout"
value="[ Checkout ]" name="action" />


+ 11
- 12
src/Web/WebMVC/Views/Shared/_Layout.cshtml View File

@ -32,11 +32,11 @@
</environment>
</head>
<body>
<header class="navbar navbar-light navbar-static-top">
<header class="esh-app-header">
<div class="container">
<article class="row">
<section class="col-lg-7 col-md-6 col-xs-12">
<section class="col-lg-7 col-md-6 col-12">
<a class="navbar-brand" routerLink="catalog">
<a asp-area="" asp-controller="Catalog" asp-action="Index">
<img src="~/images/brand.png" />
@ -96,24 +96,23 @@
if ('@User.Identity.IsAuthenticated' === 'True') {
var timerId;
let connection = stablishConnection();
connection.start().then(function () {
console.log('User Registered to Signalr Hub');
});
registerNotificationHandlers(connection);
stablishConnection((conn) => registerNotificationHandlers(conn));
}
function stablishConnection() {
return new signalR.HubConnectionBuilder()
function stablishConnection(cb) {
let connection = new signalR.HubConnectionBuilder()
.withUrl('@settings.Value.SignalrHubUrl/hub/notificationhub', {
transport: signalR.HttpTransportType.LongPolling,
accessTokenFactory: () => {
return "Authorization", getToken();
}
})
.build();
.build();
connection.start().then(function () {
console.log('User Registered to Signalr Hub');
cb(connection);
});
}
function registerNotificationHandlers(connection) {


+ 1
- 0
src/Web/WebMVC/WebMVC.csproj View File

@ -30,6 +30,7 @@
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Redis" Version="0.3.3" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Fabric.MSBuild" Version="1.6.5" />
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="1.0.163" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.5.180" />
</ItemGroup>
<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">


+ 2
- 16
src/Web/WebMVC/libman.json View File

@ -8,28 +8,14 @@
},
{
"provider": "unpkg",
"library": "bootstrap@3.3.7",
"library": "bootstrap@4.1.3",
"files": [
"dist/css/bootstrap.css",
"dist/css/bootstrap.css.map",
"dist/css/bootstrap.min.css",
"dist/css/bootstrap.min.css.map",
"dist/css/bootstrap-theme.css",
"dist/css/bootstrap-theme.css.map",
"dist/css/bootstrap-theme.min.css",
"dist/css/bootstrap-theme.min.css.map",
"dist/fonts/glyphicons-halflings-regular.eot",
"dist/fonts/glyphicons-halflings-regular.svg",
"dist/fonts/glyphicons-halflings-regular.ttf",
"dist/fonts/glyphicons-halflings-regular.woff",
"dist/fonts/glyphicons-halflings-regular.woff2",
"dist/js/bootstrap.js",
"dist/js/bootstrap.min.js",
"fonts/glyphicons-halflings-regular.eot",
"fonts/glyphicons-halflings-regular.svg",
"fonts/glyphicons-halflings-regular.ttf",
"fonts/glyphicons-halflings-regular.woff",
"fonts/glyphicons-halflings-regular.woff2"
"dist/js/bootstrap.min.js"
],
"destination": "wwwroot/lib/bootstrap/"
},


+ 4
- 0
src/Web/WebMVC/wwwroot/css/app.component.css View File

@ -12,3 +12,7 @@
width: 230px;
}
.esh-app-header {
margin: 15px;
}

+ 1
- 1
src/Web/WebMVC/wwwroot/css/app.component.min.css View File

@ -1 +1 @@
.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%;}.esh-app-footer-brand{height:50px;width:230px;}
.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%;}.esh-app-footer-brand{height:50px;width:230px;}.esh-app-header{margin:15px;}

+ 4
- 0
src/Web/WebMVC/wwwroot/css/app.component.scss View File

@ -20,4 +20,8 @@
}
}
&-header{
margin:15px;
}
}

+ 7
- 0
src/Web/WebMVC/wwwroot/css/campaigns/campaigns.component.css View File

@ -73,6 +73,12 @@
width: 80%;
}
.esh-campaigns-card-footer-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.esh-campaigns-form-button {
background-color: #83D01B;
border: none;
@ -81,6 +87,7 @@
font-size: 1rem;
transition: all 0.35s;
width: 80%;
margin-top: -15px;
}
.esh-campaigns-button.is-disabled {


+ 1
- 0
src/Web/WebMVC/wwwroot/css/catalog/catalog.component.css View File

@ -71,6 +71,7 @@
margin-top: -1.5rem;
padding: 0.5rem;
transition: all 0.35s;
margin-bottom: -6px;
}
.esh-catalog-send:hover {


+ 1
- 1
src/Web/WebMVC/wwwroot/css/catalog/catalog.component.min.css View File

@ -1 +1 @@
.esh-catalog-hero{background-image:url("../../images/main_banner.png");background-size:cover;height:260px;width:100%;}.esh-catalog-title{position:relative;top:74.28571px;}.esh-catalog-filters{background-color:#00a69c;height:65px;}.esh-catalog-filter{-webkit-appearance:none;background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;min-width:140px;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem;}.esh-catalog-filter option{background-color:#00a69c;}.esh-catalog-label{display:inline-block;position:relative;z-index:0;}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-left:.5rem;margin-top:.65rem;position:absolute;text-transform:uppercase;z-index:1;}.esh-catalog-label::after{background-image:url("../../images/arrow-down.png");content:'';height:7px;position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1;}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;margin-top:-1.5rem;padding:.5rem;transition:all .35s;}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s;}.esh-catalog-items{margin-top:1rem;}.esh-catalog-item{margin-bottom:1.5rem;text-align:center;width:33%;display:inline-block;float:none !important;}@media screen and (max-width:1024px){.esh-catalog-item{width:50%;}}@media screen and (max-width:768px){.esh-catalog-item{width:100%;}}.esh-catalog-thumbnail{max-width:370px;width:100%;}.esh-catalog-button{background-color:#83d01b;border:0;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%;}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none;}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s;}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase;}.esh-catalog-price{font-size:28px;font-weight:900;text-align:center;}.esh-catalog-price::before{content:'$';}
.esh-catalog-hero{background-image:url("../../images/main_banner.png");background-size:cover;height:260px;width:100%;}.esh-catalog-title{position:relative;top:74.28571px;}.esh-catalog-filters{background-color:#00a69c;height:65px;}.esh-catalog-filter{-webkit-appearance:none;background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;min-width:140px;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem;}.esh-catalog-filter option{background-color:#00a69c;}.esh-catalog-label{display:inline-block;position:relative;z-index:0;}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-left:.5rem;margin-top:.65rem;position:absolute;text-transform:uppercase;z-index:1;}.esh-catalog-label::after{background-image:url("../../images/arrow-down.png");content:'';height:7px;position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1;}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;margin-top:-1.5rem;padding:.5rem;transition:all .35s;margin-bottom:-6px;}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s;}.esh-catalog-items{margin-top:1rem;}.esh-catalog-item{margin-bottom:1.5rem;text-align:center;width:33%;display:inline-block;float:none !important;}@media screen and (max-width:1024px){.esh-catalog-item{width:50%;}}@media screen and (max-width:768px){.esh-catalog-item{width:100%;}}.esh-catalog-thumbnail{max-width:370px;width:100%;}.esh-catalog-button{background-color:#83d01b;border:0;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%;}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none;}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s;}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase;}.esh-catalog-price{font-size:28px;font-weight:900;text-align:center;}.esh-catalog-price::before{content:'$';}

+ 1
- 0
src/Web/WebMVC/wwwroot/css/catalog/catalog.component.scss View File

@ -80,6 +80,7 @@
margin-top: -$filter-padding * 3;
padding: $filter-padding;
transition: all $animation-speed-default;
margin-bottom: -6px;
&:hover {
background-color: $color-secondary-darker;


+ 0
- 59
src/Web/WebSPA/.angular-cli.json View File

@ -1,59 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "WebSPA"
},
"apps": [
{
"root": "Client",
"outDir": "wwwroot",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"globals.scss",
"../node_modules/bootstrap/scss/bootstrap.scss",
"../node_modules/ng2-toastr/bundles/ng2-toastr.min.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "Client/tsconfig.app.json"
},
{
"project": "Client/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "scss",
"component": {}
}
}

+ 3
- 0
src/Web/WebSPA/Client/globals.scss View File

@ -1,4 +1,7 @@
/* You can add global styles to this file, and also import other style files */
@import "~bootstrap/scss/bootstrap";
@import "~ngx-toastr/toastr-bs4-alert.scss";
@import './modules/variables';
$dist: './fonts/Montserrat-Regular';


+ 5
- 6
src/Web/WebSPA/Client/modules/app.component.html View File

@ -1,18 +1,17 @@
<header class="navbar navbar-light navbar-static-top">
<header class="esh-app-header">
<div class="container">
<article class="row">
<section class="col-lg-7 col-md-6 col-xs-12">
<section class="col-lg-7 col-md-6 col-12">
<a class="navbar-brand" routerLink="catalog">
<img src="assets/images/brand.png" />
</a>
</section>
<section class="col-lg-4 col-md-5 col-xs-12">
<section class="col-lg-4 col-md-5 col-12">
<esh-identity></esh-identity>
</section>
<section class="col-lg-1 col-xs-12">
<section class="col-lg-1 col-12">
<esh-basket-status *ngIf="Authenticated"></esh-basket-status>
</section>
@ -32,7 +31,7 @@
</section>
<section class="col-sm-6">
<img class="esh-app-footer-text hidden-xs" src="assets/images/main_footer_text.png" width="335" height="26" alt="footer text image" />
<img class="esh-app-footer-text hidden" src="assets/images/main_footer_text.png" width="335" height="26" alt="footer text image" />
</section>
</article>


+ 4
- 0
src/Web/WebSPA/Client/modules/app.component.scss View File

@ -11,6 +11,7 @@
padding-bottom: $padding;
padding-top: $padding;
width: 100%;
bottom: 0;
$height: 50px;
@ -18,6 +19,9 @@
height: $height;
width: 230px;
}
}
&-header {
margin: 15px;
}
}

+ 6
- 8
src/Web/WebSPA/Client/modules/app.component.ts View File

@ -1,19 +1,16 @@
import { Title } from '@angular/platform-browser';
import { Component, ViewEncapsulation, OnInit, ViewContainerRef } from '@angular/core';
import { RouterModule } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
import { Component, OnInit, ViewContainerRef } from '@angular/core';
import { Subscription } from 'rxjs';
import { DataService } from './shared/services/data.service';
import { SecurityService } from './shared/services/security.service';
import { ConfigurationService } from './shared/services/configuration.service';
import { SignalrService } from './shared/services/signalr.service';
import { ToastsManager } from 'ng2-toastr';
import { ToastrService } from 'ngx-toastr';
/*
* App Component
* Top Level Component
*/
@Component({
selector: 'esh-app',
styleUrls: ['./app.component.scss'],
@ -27,10 +24,11 @@ export class AppComponent implements OnInit {
private securityService: SecurityService,
private configurationService: ConfigurationService,
private signalrService: SignalrService,
private toastr: ToastsManager,
private toastr: ToastrService,
vcr: ViewContainerRef
) {
this.toastr.setRootViewContainerRef(vcr);
// TODO: Set Taster Root (Overlay) container
//this.toastr.setRootViewContainerRef(vcr);
this.Authenticated = this.securityService.IsAuthorized;
}


+ 11
- 14
src/Web/WebSPA/Client/modules/app.module.ts View File

@ -1,35 +1,32 @@
import { NgModule, NgModuleFactoryLoader } from '@angular/core';
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { BrowserModule } from '@angular/platform-browser';
// import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from "@angular/common/http";
import { routing } from './app.routes';
import { routing } from './app.routes';
import { AppService } from './app.service';
import { AppComponent } from './app.component';
import { SharedModule } from './shared/shared.module';
import { CatalogModule } from './catalog/catalog.module';
import { OrdersModule } from './orders/orders.module';
import { SharedModule } from './shared/shared.module';
import { CatalogModule } from './catalog/catalog.module';
import { OrdersModule } from './orders/orders.module';
import { BasketModule } from './basket/basket.module';
import { CampaignsModule } from './campaigns/campaigns.module';
import { ToastModule } from 'ng2-toastr/ng2-toastr';
import { ToastrModule } from 'ngx-toastr';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserAnimationsModule,
BrowserModule,
ToastModule.forRoot(),
ToastrModule.forRoot(),
routing,
HttpModule,
HttpClientModule,
// Only module that app module loads
SharedModule.forRoot(),
CatalogModule,
OrdersModule,
BasketModule,
CampaignsModule
CampaignsModule
],
providers: [
AppService


+ 1
- 1
src/Web/WebSPA/Client/modules/basket/basket-status/basket-status.component.ts View File

@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Subscription } from 'rxjs';
import { BasketService } from '../basket.service';
import { BasketWrapperService } from '../../shared/services/basket.wrapper.service';


+ 16
- 16
src/Web/WebSPA/Client/modules/basket/basket.component.html View File

@ -9,11 +9,11 @@
</div>
<article class="esh-basket-titles row">
<section class="esh-basket-title col-xs-3">Product</section>
<section class="esh-basket-title col-xs-3 hidden-lg-down"></section>
<section class="esh-basket-title col-xs-2">Price</section>
<section class="esh-basket-title col-xs-2">Quantity</section>
<section class="esh-basket-title col-xs-2">Cost</section>
<section class="esh-basket-title col-3">Product</section>
<section class="esh-basket-title col-3 hidden-lg-down"></section>
<section class="esh-basket-title col-2">Price</section>
<section class="esh-basket-title col-2">Quantity</section>
<section class="esh-basket-title col-2">Cost</section>
</article>
<div *ngFor="let item of basket?.items" class="esh-basket-items--border">
@ -22,9 +22,9 @@
<section class="esh-basket-item esh-basket-item--middle col-lg-3 hidden-lg-down">
<img class="esh-basket-image" src="{{item.pictureUrl}}"/>
</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-3">{{item.productName}}</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2">$ {{item.unitPrice | number:'.2-2'}}</section>
<section class="esh-basket-item esh-basket-item--middle col-xs-2">
<section class="esh-basket-item esh-basket-item--middle col-3">{{item.productName}}</section>
<section class="esh-basket-item esh-basket-item--middle col-2">$ {{item.unitPrice | number:'.2-2'}}</section>
<section class="esh-basket-item esh-basket-item--middle col-2">
<input id="quantity"
class="esh-basket-input"
type="number"
@ -32,7 +32,7 @@
[(ngModel)]="item.quantity"
(change)="itemQuantityChanged(item)"/>
</section>
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-xs-2">$ {{(item.unitPrice * item.quantity) | number:'.2-2'}}</section>
<section class="esh-basket-item esh-basket-item--middle esh-basket-item--mark col-2">$ {{(item.unitPrice * item.quantity) | number:'.2-2'}}</section>
</article>
<br/>
<div class="esh-basket-items-margin-left1 row">
@ -43,21 +43,21 @@
<div class="container">
<article class="esh-basket-titles esh-basket-titles--clean row">
<section class="esh-basket-title col-xs-9"></section>
<section class="esh-basket-title col-xs-2">Total</section>
<section class="esh-basket-title col-9"></section>
<section class="esh-basket-title col-2">Total</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-9"></section>
<section class="esh-basket-item esh-basket-item--mark col-xs-2">$ {{totalPrice | number:'.2-2'}}</section>
<section class="esh-basket-item col-9"></section>
<section class="esh-basket-item esh-basket-item--mark col-2">$ {{totalPrice | number:'.2-2'}}</section>
</article>
<article class="esh-basket-items row">
<section class="esh-basket-item col-xs-7"></section>
<section class="esh-basket-item col-xs-2">
<section class="esh-basket-item col-7"></section>
<section class="esh-basket-item col-2">
<button class="btn esh-basket-checkout" (click)="update($event)">[ Update ]</button>
</section>
<section class="esh-basket-item col-xs-3">
<section class="esh-basket-item col-3">
<div (click)="checkOut($event)" class="btn esh-basket-checkout">[ Checkout ]</div>
</section>
</article>


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

Loading…
Cancel
Save