This commit is contained in:
Ramón Tomás 2017-03-03 17:52:05 +01:00
commit 7db5119a87
204 changed files with 29130 additions and 645 deletions

12
build-bits.sh → CLI-Mac/build-bits.sh Executable file → Normal file
View File

@ -1,12 +1,12 @@
#!/bin/sh
projectList=(
"src/Services/Catalog/Catalog.API"
"src/Services/Basket/Basket.API"
"src/Services/Ordering/Ordering.API"
"src/Services/Identity/Identity.API"
"src/Web/WebMVC"
"src/Web/WebSPA"
"../src/Services/Catalog/Catalog.API"
"../src/Services/Basket/Basket.API"
"../src/Services/Ordering/Ordering.API"
"../src/Services/Identity/Identity.API"
"../src/Web/WebMVC"
"../src/Web/WebSPA"
)
for project in "${projectList[@]}"

View File

@ -1,9 +1,15 @@
Param([string] $rootPath)
$scriptPath = Split-Path $script:MyInvocation.MyCommand.Path
Write-Host "Current script directory is $scriptPath" -ForegroundColor Yellow
if ([string]::IsNullOrEmpty($rootPath)) {
$rootPath = "$scriptPath\.."
}
Write-Host "Root path used is $rootPath" -ForegroundColor Yellow
# *** WebMVC paths ***
$webMVCPath = $scriptPath + "\src\Web\WebMVC"
$webMVCPath = $rootPath + "\src\Web\WebMVC"
$webMVCPathToProject = $webMVCPath + "\WebMVC.csproj"
Write-Host "webMVCPathToProject is $webMVCPathToProject" -ForegroundColor Yellow
$webMVCPathToPub = $webMVCPath + "\obj\Docker\publish"
@ -11,7 +17,7 @@ Write-Host "webMVCPathToPub is $webMVCPathToPub" -ForegroundColor Yellow
# *** WebSPA paths ***
$webSPAPath = $scriptPath + "\src\Web\WebSPA"
$webSPAPath = $rootPath + "\src\Web\WebSPA"
$webSPAPathToProject = $webSPAPath + "\WebSPA.csproj"
Write-Host "webSPAPathToProject is $webSPAPathToProject" -ForegroundColor Yellow
$webSPAPathToPub = $webSPAPath + "\obj\Docker\publish"
@ -19,7 +25,7 @@ Write-Host "webSPAPathToPub is $webSPAPathToPub" -ForegroundColor Yellow
# *** IdentitySvc paths ***
$identitySvcPath = $scriptPath + "\src\Services\Identity\Identity.API"
$identitySvcPath = $rootPath + "\src\Services\Identity\Identity.API"
$identitySvcToProject = $identitySvcPath + "\Identity.API.csproj"
Write-Host "identitySvcToProject is $identitySvcToProject" -ForegroundColor Yellow
$identitySvcPathToPub = $identitySvcPath + "\obj\Docker\publish"
@ -27,7 +33,7 @@ Write-Host "identitySvcPathToPub is $identitySvcPathToPub" -ForegroundColor Yell
# *** Catalog paths ***
$catalogPath = $scriptPath + "\src\Services\Catalog\Catalog.API"
$catalogPath = $rootPath + "\src\Services\Catalog\Catalog.API"
$catalogPathToProject = $catalogPath + "\Catalog.API.csproj"
Write-Host "catalogPathToProject is $catalogPathToProject" -ForegroundColor Yellow
$catalogPathToPub = $catalogPath + "\obj\Docker\publish"
@ -35,14 +41,14 @@ Write-Host "catalogPathToPub is $catalogPathToPub" -ForegroundColor Yellow
# *** Ordering paths ***
$orderingPath = $scriptPath + "\src\Services\Ordering\Ordering.API"
$orderingPath = $rootPath + "\src\Services\Ordering\Ordering.API"
$orderingPathToProject = $orderingPath + "\Ordering.API.csproj"
Write-Host "orderingPathToProject is $orderingPathToProject" -ForegroundColor Yellow
$orderingPathToPub = $orderingPath + "\obj\Docker\publish"
Write-Host "orderingPathToPub is $orderingPathToPub" -ForegroundColor Yellow
# *** Basket paths ***
$basketPath = $scriptPath + "\src\Services\Basket\Basket.API"
$basketPath = $rootPath + "\src\Services\Basket\Basket.API"
$basketPathToProject = $basketPath + "\Basket.API.csproj"
Write-Host "basketPathToProject is $basketPathToProject" -ForegroundColor Yellow
$basketPathToPub = $basketPath + "\obj\Docker\publish"

View File

@ -0,0 +1,52 @@
Param([string] $rootPath)
$scriptPath = Split-Path $script:MyInvocation.MyCommand.Path
Write-Host "Current script directory is $scriptPath" -ForegroundColor Yellow
if ([string]::IsNullOrEmpty($rootPath)) {
$rootPath = "$scriptPath\.."
}
Write-Host "Root path used is $rootPath" -ForegroundColor Yellow
$projectPaths =
@{Path="$rootPath\src\Web\WebMVC";Prj="WebMVC.csproj"},
@{Path="$rootPath\src\Web\WebSPA";Prj="WebSPA.csproj"},
@{Path="$rootPath\src\Services\Identity\Identity.API";Prj="Identity.API.csproj"},
@{Path="$rootPath\src\Services\Catalog\Catalog.API";Prj="Catalog.API.csproj"},
@{Path="$rootPath\src\Services\Ordering\Ordering.API";Prj="Ordering.API.csproj"},
@{Path="$rootPath\src\Services\Basket\Basket.API";Prj="Basket.API.csproj"}
$projectPaths | foreach {
$projectPath = $_.Path
$projectFile = $_.Prj
$outPath = $_.Path + "\obj\Docker\publish"
$projectPathAndFile = "$projectPath\$projectFile"
Write-Host "Deleting $outPath" -ForegroundColor Yellow
remove-item -path $outPath -Force -Recurse -ErrorAction SilentlyContinue
Write-Host "Publishing $projectPathAndFile to $outPath" -ForegroundColor Yellow
dotnet restore $projectPathAndFile
dotnet build $projectPathAndFile
dotnet publish $projectPathAndFile -o $outPath
}
########################################################################################
# Delete old eShop Docker images
########################################################################################
$imagesToDelete = docker images --filter=reference="eshop/*" -q
If (-Not $imagesToDelete) {Write-Host "Not deleting eShop images as there are no eShop images in the current local Docker repo."}
Else
{
# Delete all containers
Write-Host "Deleting all containers in local Docker Host"
docker rm $(docker ps -a -q) -f
# Delete all eshop images
Write-Host "Deleting eShop images in local Docker repo"
Write-Host $imagesToDelete
docker rmi $(docker images --filter=reference="eshop/*" -q) -f
}
# WE DON'T NEED DOCKER BUILD AS WE CAN RUN "DOCKER-COMPOSE BUILD" OR "DOCKER-COMPOSE UP" AND IT WILL BUILD ALL THE IMAGES IN THE .YML FOR US

View File

@ -1,7 +1,3 @@
$scriptPath = Split-Path $script:MyInvocation.MyCommand.Path
Write-Host "Current script directory is $scriptPath" -ForegroundColor Yellow
$imagesToDelete = docker images --filter=reference="eshop/*" -q
If (-Not $imagesToDelete) {Write-Host "Not deleting eShop images as there are no eShop images in the current local Docker repo."}

View File

@ -0,0 +1,11 @@
Param([string] $rootPath)
$scriptPath = Split-Path $script:MyInvocation.MyCommand.Path
Write-Host "Current script directory is $scriptPath" -ForegroundColor Yellow
if ([string]::IsNullOrEmpty($rootPath)) {
$rootPath = "$scriptPath\.."
}
Write-Host "Root path used is $rootPath" -ForegroundColor Yellow
docker-compose -f "$rootPath\docker-compose-external.yml" -f "$rootPath\docker-compose-external.override.yml" up

View File

@ -0,0 +1,9 @@
Param([string] $rootPath)
$scriptPath = Split-Path $script:MyInvocation.MyCommand.Path
if ([string]::IsNullOrEmpty($rootPath)) {
$rootPath = "$scriptPath\.."
}
Write-Host "Root path used is $rootPath" -ForegroundColor Yellow
& .\build-bits.ps1 -rootPath $rootPath
docker-compose -f "$rootPath\docker-compose-windows.yml" -f "$rootPath\docker-compose-windows.override.yml" up

View File

@ -2,7 +2,9 @@
Sample .NET Core reference application, powered by Microsoft, based on a simplified microservices architecture and Docker containers. <p>
> ### DISCLAIMER
> This reference application proposes a simplified microservice oriented architecture implementation (currently in ALPHA state) to introduce technologies like .NET Core with Docker containers through a comprehensive but simplified application. However, this reference application it is not trying to solve all the problems in a large 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.
> IMPORTANT: The current state of this sample application is ALPHA, therefore, many areas could change significantly while refactoring and getting improvements. Feedback and pull requests from the community will be appreciated.
>
> This reference application proposes a simplified microservice oriented architecture implementation (as mentioned, currently in ALPHA state) to introduce technologies like .NET Core with Docker containers through a comprehensive but simplified application. However, this reference application it is not trying to solve all the problems in a large 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 (still not covered here) after understanding Docker containers and microservices is to select a microservice cluster/orchestrator like Docker Swarm, Kubernetes or DC/OS (in Azure Container Service) or Azure Service Fabric which in most of the cases will require additional partial changes to your application's configuration (although the present architecture should work on most orchestrators with small changes). In the future we might fork this project and make multiple versions targeting specific microservice cluster/orchestrators. <p>
> Read the planned <a href='https://github.com/dotnet/eShopOnContainers/wiki/Roadmap-and-Milestones-for-future-releases'>Roadmap and Milestones for future releases of eShopOnContainers</a> within the Wiki for further info about possible new implementations and provide feedback at the ISSUES level if you'd like to see any specific scenario implemented.

86
README.md.saved.bak Normal file
View File

@ -0,0 +1,86 @@
# eShopOnContainers - Microservices Architecture and Containers based Reference Application (**ALPHA state** - VS 2017 and CLI environments compatible)
Sample .NET Core reference application, powered by Microsoft, based on a simplified microservices architecture and Docker containers. <p>
> ### DISCLAIMER
> IMPORTANT: The current state of this sample application is ALPHA, therefore, many areas could change significantly while refactoring and getting improvements. Feedback and pull requests from the community will be appreciated.
>
> This reference application proposes a simplified microservice oriented architecture implementation (as mentioned, currently in ALPHA state) to introduce technologies like .NET Core with Docker containers through a comprehensive but simplified application. However, this reference application it is not trying to solve all the problems in a large 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 (still not covered here) after understanding Docker containers and microservices is to select a microservice cluster/orchestrator like Docker Swarm, Kubernetes or DC/OS (in Azure Container Service) or Azure Service Fabric which in most of the cases will require additional partial changes to your application's configuration (although the present architecture should work on most orchestrators with small changes). In the future we might fork this project and make multiple versions targeting specific microservice cluster/orchestrators. <p>
> Read the planned <a href='https://github.com/dotnet/eShopOnContainers/wiki/Roadmap-and-Milestones-for-future-releases'>Roadmap and Milestones for future releases of eShopOnContainers</a> within the Wiki for further info about possible new implementations and provide feedback at the ISSUES level if you'd like to see any specific scenario implemented.
<p>
This reference application is cross-platform either in the server and client side, thanks to .NET Core services capable of running on Linux or Windows containers depending on your Docker host, and to Xamarin for mobile apps running on Android, iOS or Windows/UWP plus any browser for the client web apps.
<p>
<img src="img/eshop_logo.png">
<img src="img/eShopOnContainers_Architecture_Diagram.png">
<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 for Linux container (a single shared Docker container for SQL databases) so the whole solution can be up and running without any dependency in the cloud or server. Each database could also be deployed as a single Docker container, but then you'd need more then 8GB or memory RAM 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 regards Redis cache running as a container for the development environment.
> <p> However, in a real production environment it is recommended to have persistance (SQL Server and Redis) in HA services like Azure SQL Database, Redis as a service or any other clustering system. 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 HA cloud or on-premises.
## Related documentation and guidance
While developing this reference application, we've been creating a reference Guide/eBook named <b>"Architecting and Developing Containerized and Microservice based .NET Applications"</b> 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 that can also live as Docker containers.
<p>
There's also an additional eBook focusing on Containers/Docker lifecycle (DevOps, CI/CD, etc.) with Microsoft Tools, already published.
You can start reviewing these Guides/eBooks here:
<p>
You can download both eBooks from here:
| Architecting & Developing | Containers Lifecycle & CI/CD |
| ------------ | ------------|
| <a href='docs/architecting-and-developing-containerized-and-microservice-based-net-applications-ebook-early-draft.pdf'><img src="img/ebook_arch_dev_microservices_containers_cover.png"> </a> | <a href='https://aka.ms/dockerlifecycleebook'> <img src="img/ebook_containers_lifecycle.png"> </a> |
| <a href='docs/architecting-and-developing-containerized-and-microservice-based-net-applications-ebook-early-draft.pdf'>Download (Confidential DRAFT until published)</a> | <a href='https://aka.ms/dockerlifecycleebook'>Download</a> |
<p>However, we encourage to review the "Architecting/Developing" eBook because the architectural styles and architectural patterns and technologies explained in the guidance are using this reference application when explaining many sample implementations.
## 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 screenshot below shows the VS Solution structure for those microservices/containers and client apps.
- Open <b>eShopOnContainers.sln</b> for a solution containing all the projects (All client apps and services).
- Open <b>eShopOnContainers-ServicesAndWebApps.sln</b> for a solution containing just the server-side projects related to the microservices and web applications.
- Open <b>eShopOnContainers-MobileApps.sln</b> for a solution containing just the client mobile app projects (Xamarin mobile apps only).
<img src="img/vs-solution-structure.png">
Finally, those microservices are consumed by multiple client web and mobile apps, as described below.
<b>*MVC Application (ASP.NET Core)*</b>: Its an MVC 6 application where you can find interesting scenarios on how to consume HTTP-based microservices from C# running in the server side, as it is a typical ASP.NET Core MVC application. Since it is a server-side application, access to other containers/microservices is done within the internal Docker Host network with its internal name resolution.
<img src="img/eshop-webmvc-app-screenshot.png">
<b>*SPA (Single Page Application)*</b>: Providing similar "eShop business functionality" but developed with Angular 2, Typescript and slightly using ASP.NET Core MVC 6. This is another approach for client web applications to be used when you want to have a more modern client behavior which is not behaving with the typical browser round-trip on every action but behaving like a Single-Page-Application which is more similar to a desktop app usage experience. The consumption of the HTTP-based microservices is done from TypeScript/JavaScript in the client browser, so the client calls to the microservices come from out of the Docker Host internal network (Like from your network or even from the Internet).
<img src="img/eshop-webspa-app-screenshot.png">
<b>*Xamarin Mobile App (For iOS, Android and Windows/UWP)*</b>: It is a client mobile app supporting the most common mobilee OS platforms (iOS, Android and Windows/UWP). In this case, the consumption of the microservices is done from C# but running on the client devices, so out of the Docker Host internal network (Like from your network or even the Internet).
<img src="img/xamarin-mobile-App.png">
> ### Note on tested Docker Containers/Images
> The development and testing of this project was (as of January 2017) done <b>only on Docker Linux containers</b> running in development machines with "Docker for Windows" and the default Hyper-V Linux VM (MobiLinuxVM) installed by "Docker for Windows".
The <b>Windows Containers scenario has not been implemented/tested yet</b>, but the application should be able to run on Windows Containers based on different Docker base images, as well, as the .NET Core services have also been tested running on plain Windows (with no Docker).
The app was also partially tested on "Docker for Mac" using a development MacOS machine with .NET Core and VS Code installed. However, that is still a scenario using Linux containers running on the VM setup in the Mac by the "Docker for Windows" setup.
## Setting up your development environment for eShopOnContainers
### Visual Studio 2017 and Windows based
This is the more straightforward way to get started:
https://github.com/dotnet/eShopOnContainers/wiki/02.-Setting-eShopOnContainer-solution-up-in-a-Visual-Studio-2017-environment
### CLI and Windows based
For those who prefer the CLI on Windows, using dotnet CLI, docker CLI and VS Code for Windows:
https://github.com/dotnet/eShopOnContainers/wiki/03.-Setting-the-eShopOnContainers-solution-up-in-a-Windows-CLI-environment-(dotnet-CLI,-Docker-CLI-and-VS-Code)
### CLI and Mac based
For those who prefer the CLI on a Mac, using dotnet CLI, docker CLI and VS Code for Mac:
https://github.com/dotnet/eShopOnContainers/wiki/04.-Setting-eShopOnContainer-solution-up-in-a-Mac,-VS-Code-and-CLI-environment--(dotnet-CLI,-Docker-CLI-and-VS-Code)
> ### Note on Windows Containers
> As mentioned, the development and testing of this project (January 2017 version) was done on Docker Linux containers running in development machines with "Docker for Windows" and the default Hyper-V Linux VM (MobiLinuxVM) installed by "Docker for Windows".
In order to run the application on Windows Containers you'd need to change the base images used by each container:
> - Official .NET Core base-image for Windows Containers, at Docker Hub: https://hub.docker.com/r/microsoft/dotnet/ (Using the Windows Nanoserver tag)
> - Official base-image for SQL Server on Windows Containers, at Docker Hub: https://hub.docker.com/r/microsoft/mssql-server-windows

View File

@ -1,9 +0,0 @@
version: '2'
services:
ci-build:
image: microsoft/aspnetcore-build:1.0-1.1
volumes:
- .:/src
working_dir: /src
command: /bin/bash -c "dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish"

View File

@ -1,69 +0,0 @@
version: '2'
services:
basket.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionString=basket.data
#- identityUrl=http://13.88.8.119:5105 #Remote: VM Needs to have public access at 5105.
- identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
ports:
- "5103:5103"
catalog.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word
#- ExternalCatalogBaseUrl=http://13.88.8.119:5101 #Remote: VM Needs to have public access at 5105.
- ExternalCatalogBaseUrl=http://localhost:5101 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
ports:
- "5101:5101"
identity.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- SpaClient=http://localhost:5104
- ConnectionStrings__DefaultConnection=Server=sql.data;Database=Microsoft.eShopOnContainers.Service.IdentityDb;User Id=sa;Password=Pass@word
#- MvcClient=http://13.88.8.119:5100 #Remote: VM Needs to have public access at 5105.
- MvcClient=http://localhost:5100 #Local: You need to open your local dev-machine firewall at range 5100-5105.
ports:
- "5105:5105"
ordering.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
#- identityUrl=http://13.88.8.119:5105 #Remote: VM Needs to have public access at 5105.
- identityUrl=http://identity.api:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
ports:
- "5102:5102"
webspa:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- CatalogUrl=http://localhost:5101
- OrderingUrl=http://localhost:5102
#- IdentityUrl=http://13.88.8.119:5105 #Remote: VM Needs to have public access at 5105.
- IdentityUrl=http://localhost:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- BasketUrl=http://localhost:5103
ports:
- "5104:5104"
webmvc:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- CatalogUrl=http://catalog.api:5101
- OrderingUrl=http://ordering.api:5102
#- IdentityUrl=http://13.88.8.119:5105 #Remote: VM Needs to have public access at 5105.
- IdentityUrl=http://10.0.75.1:5105 #Local: You need to open your local dev-machine firewall at range 5100-5105. at range 5100-5105.
- BasketUrl=http://basket.api:5103
ports:
- "5100:5100"
sql.data:
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5433:1433"

View File

@ -1,92 +0,0 @@
version: '2'
services:
basket.api:
image: eshop/basket.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Basket/Basket.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
catalog.api:
image: eshop/catalog.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Catalog/Catalog.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
identity.api:
image: eshop/identity.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Identity/Identity.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
ordering.api:
image: eshop/ordering.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Ordering/Ordering.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
webspa:
image: eshop/webspa:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Web/WebSPA:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
webmvc:
image: eshop/webmvc:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Web/WebMVC:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"

View File

@ -1,62 +0,0 @@
version: '2'
services:
basket.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
catalog.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
identity.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
ordering.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
webspa:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
webmvc:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"

View File

@ -1,63 +0,0 @@
version: '2'
services:
basket.api:
image: eshop/basket.api
build:
context: ./src/Services/Basket/Basket.API
dockerfile: Dockerfile
depends_on:
- basket.data
- identity.api
catalog.api:
image: eshop/catalog.api
build:
context: ./src/Services/Catalog/Catalog.API
dockerfile: Dockerfile
depends_on:
- sql.data
identity.api:
image: eshop/identity.api
build:
context: ./src/Services/Identity/Identity.API
dockerfile: Dockerfile
depends_on:
- sql.data
ordering.api:
image: eshop/ordering.api
build:
context: ./src/Services/Ordering/Ordering.API
dockerfile: Dockerfile
depends_on:
- sql.data
webspa:
image: eshop/webspa
build:
context: ./src/Web/WebSPA
dockerfile: Dockerfile
depends_on:
- identity.api
- basket.api
webmvc:
image: eshop/webmvc
build:
context: ./src/Web/WebMVC
dockerfile: Dockerfile
depends_on:
- catalog.api
- ordering.api
- identity.api
- basket.api
sql.data:
image: microsoft/mssql-server-linux
basket.data:
image: redis
ports:
- "6379:6379"

View File

@ -1,9 +0,0 @@
version: '2'
services:
ci-build:
image: microsoft/aspnetcore-build:1.0-1.1
volumes:
- .:/src
working_dir: /src
command: /bin/bash -c "dotnet restore ./eShopOnContainers-ServicesAndWebApps.sln && dotnet publish ./eShopOnContainers-ServicesAndWebApps.sln -c Release -o ./obj/Docker/publish"

View File

@ -1,92 +0,0 @@
version: '2.1'
services:
basket.api:
image: eshop/basket.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Basket/Basket.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"
catalog.api:
image: eshop/catalog.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Catalog/Catalog.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"
identity.api:
image: eshop/identity.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Identity/Identity.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"
ordering.api:
image: eshop/ordering.api:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Services/Ordering/Ordering.API:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"
webspa:
image: eshop/webspa:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Web/WebSPA:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"
webmvc:
image: eshop/webmvc:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./src/Web/WebMVC:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"

View File

@ -1,62 +0,0 @@
version: '2.1'
services:
basket.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"
catalog.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"
identity.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"
ordering.api:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"
webspa:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"
webmvc:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=windows"

View File

@ -0,0 +1,28 @@
# The MSI installs a service which is hard to override, so let's use a zip file.
FROM microsoft/windowsservercore
MAINTAINER alexellis2@gmail.com
SHELL ["powershell"]
RUN $ErrorActionPreference = 'Stop'; \
wget https://github.com/MSOpenTech/redis/releases/download/win-3.2.100/Redis-x64-3.2.100.zip -OutFile Redis-x64-3.2.100.zip ; \
Expand-Archive Redis-x64-3.2.100.zip -dest 'C:\\Program Files\\Redis\\' ; \
Remove-Item Redis-x64-3.2.100.zip -Force
RUN setx PATH '%PATH%;C:\\Program Files\\Redis\\'
WORKDIR 'C:\\Program Files\\Redis\\'
RUN Get-Content redis.windows.conf | Where { $_ -notmatch 'bind 127.0.0.1' } | Set-Content redis.openport.conf ; \
Get-Content redis.openport.conf | Where { $_ -notmatch 'protected-mode yes' } | Set-Content redis.unprotected.conf ; \
Add-Content redis.unprotected.conf 'protected-mode no' ; \
Add-Content redis.unprotected.conf 'bind 0.0.0.0' ; \
Get-Content redis.unprotected.conf
EXPOSE 6379
# Define our command to be run when launching the container
CMD .\\redis-server.exe .\\redis.unprotected.conf --port 6379 ; \
Write-Host Redis Started... ; \
while ($true) { Start-Sleep -Seconds 3600 }

View File

@ -0,0 +1,9 @@
version: '2'
services:
sql.data:
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5433:1433"

View File

@ -0,0 +1,10 @@
version: '2'
services:
sql.data:
image: microsoft/mssql-server-linux
basket.data:
image: redis
ports:
- "6379:6379"

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectGuid>2ede831a-98f5-4f23-b2db-7e9dc935766a</ProjectGuid>
<DockerLaunchBrowser>True</DockerLaunchBrowser>
<DockerServiceUrl>http://localhost:5100</DockerServiceUrl>
<DockerServiceName>webmvc</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose-windows.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose-windows.vs.debug.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose-windows.yml" />
</ItemGroup>
</Project>

View File

@ -5,7 +5,7 @@ services:
image: eshop/basket.api
build:
context: ./src/Services/Basket/Basket.API
dockerfile: Dockerfile
dockerfile: Dockerfile.nanowin
depends_on:
- basket.data
- identity.api
@ -14,7 +14,7 @@ services:
image: eshop/catalog.api
build:
context: ./src/Services/Catalog/Catalog.API
dockerfile: Dockerfile
dockerfile: Dockerfile.nanowin
depends_on:
- sql.data
@ -22,7 +22,7 @@ services:
image: eshop/identity.api
build:
context: ./src/Services/Identity/Identity.API
dockerfile: Dockerfile
dockerfile: Dockerfile.nanowin
depends_on:
- sql.data
@ -30,7 +30,7 @@ services:
image: eshop/ordering.api
build:
context: ./src/Services/Ordering/Ordering.API
dockerfile: Dockerfile
dockerfile: Dockerfile.nanowin
depends_on:
- sql.data
@ -38,7 +38,7 @@ services:
image: eshop/webspa
build:
context: ./src/Web/WebSPA
dockerfile: Dockerfile
dockerfile: Dockerfile.nanowin
depends_on:
- identity.api
- basket.api
@ -47,7 +47,7 @@ services:
image: eshop/webmvc
build:
context: ./src/Web/WebMVC
dockerfile: Dockerfile
dockerfile: Dockerfile.nanowin
depends_on:
- catalog.api
- ordering.api
@ -60,8 +60,8 @@ services:
basket.data:
image: eshop/redis
build:
context: ./extradf
dockerfile: Dockerfile
context: ./_docker/redis
dockerfile: Dockerfile.nanowin
ports:
- "6379:6379"
networks:

View File

@ -1,49 +0,0 @@
Param(
[ValidateSet(nanowin", "linux")] [String] [Parameter(Mandatory=$true)] $Platform,
[bool] $DeleteImages = $false
)
$scriptPath = Split-Path $script:MyInvocation.MyCommand.Path
$Platform = $Platform.ToLowerInvariant()
$SourcePerPlatformDockerFilesPath = "$ScriptPath\_docker\$Platform\extradf"
$TargetPerPlatformDockerFilesPath = "$ScriptPath\extradf"
Write-Host "Current script directory is $scriptPath" -ForegroundColor Yellow
If ($DeleteImages) {
Write-Host "Deleting eShop Docker images"
& "$ScriptPath\delete-images.ps1"
}
If (Test-Path $TargetPerPlatformDockerFilesPath) {
Write-Host "Found per-platform extra Docker files. Removing..."
Remove-Item "$TargetPerPlatformDockerFilesPath\" -Recurse -Force
}
If (Test-Path $SourcePerPlatformDockerFilesPath) {
Write-Host "Copying per-platform extra Dockerfiles"
Copy-Item "$SourcePerPlatformDockerFilesPath\*" "$ScriptPath\extradf\" -Recurse -Force
}
else {
Write-Host "There are not extra Dockerfiles for platform $Platform"
}
Write-Host "Changing Dockerfiles"
Copy-Item "$ScriptPath\src\Services\Basket\Basket.API\Dockerfile.$Platform" "$ScriptPath\src\Services\Basket\Basket.API\Dockerfile" -Force
Copy-Item "$ScriptPath\src\Services\Catalog\Catalog.API\Dockerfile.$Platform" "$ScriptPath\src\Services\Catalog\Catalog.API\Dockerfile" -Force
Copy-Item "$ScriptPath\src\Services\Identity\Identity.API\Dockerfile.$Platform" "$ScriptPath\src\Services\Identity\Identity.API\Dockerfile" -Force
Copy-Item "$ScriptPath\src\Services\Ordering\Ordering.API\Dockerfile.$Platform" "$ScriptPath\src\Services\Ordering\Ordering.API\Dockerfile" -Force
Copy-Item "$ScriptPath\src\Web\WebMVC\Dockerfile.$Platform" "$ScriptPath\src\Web\WebMVC\Dockerfile" -Force
Copy-Item "$ScriptPath\src\Web\WebSPA\Dockerfile.$Platform" "$ScriptPath\src\Web\WebSPA\Dockerfile" -Force
Write-Host "Replacing Docker-compose"
Copy-Item "$ScriptPath\_docker\$Platform\*.yml" "$ScriptPath\" -Force
Remove-Item "$ScriptPath\.eshopdocker_*" -Force -ErrorAction SilentlyContinue
New-Item "$ScriptPath\.eshopdocker_$Platform" -ItemType File | Out-Null
Write-Host "Done. Docker files are set for platform: $Platform"

View File

@ -1,6 +0,0 @@
FROM microsoft/aspnetcore:1.1
ARG source
WORKDIR /app
EXPOSE 80
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", "Basket.API.dll"]

View File

@ -49,7 +49,7 @@
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0-msbuild3-final" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
</ItemGroup>
<ItemGroup>

View File

@ -1,6 +0,0 @@
FROM microsoft/aspnetcore:1.1
ARG source
WORKDIR /app
EXPOSE 80
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", "Catalog.API.dll"]

View File

@ -12,6 +12,7 @@
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@ -28,7 +29,7 @@
if (env.IsDevelopment())
{
builder.AddUserSecrets();
builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly);
}
builder.AddEnvironmentVariables();

View File

@ -124,7 +124,7 @@ namespace WebMVC.Migrations
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser", b =>
modelBuilder.Entity("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser", b =>
{
b.Property<string>("Id");
@ -211,7 +211,7 @@ namespace WebMVC.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser")
b.HasOne("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser")
.WithMany("Claims")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
@ -219,7 +219,7 @@ namespace WebMVC.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser")
b.HasOne("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser")
.WithMany("Logins")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
@ -232,7 +232,7 @@ namespace WebMVC.Migrations
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser")
b.HasOne("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser")
.WithMany("Roles")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);

View File

@ -124,7 +124,7 @@ namespace WebMVC.Migrations
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser", b =>
modelBuilder.Entity("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser", b =>
{
b.Property<string>("Id");
@ -215,7 +215,7 @@ namespace WebMVC.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser")
b.HasOne("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser")
.WithMany("Claims")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
@ -223,7 +223,7 @@ namespace WebMVC.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser")
b.HasOne("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser")
.WithMany("Logins")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
@ -236,7 +236,7 @@ namespace WebMVC.Migrations
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser")
b.HasOne("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser")
.WithMany("Roles")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);

View File

@ -122,7 +122,7 @@ namespace WebMVC.Migrations
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser", b =>
modelBuilder.Entity("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser", b =>
{
b.Property<string>("Id");
@ -213,7 +213,7 @@ namespace WebMVC.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser")
b.HasOne("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser")
.WithMany("Claims")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
@ -221,7 +221,7 @@ namespace WebMVC.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser")
b.HasOne("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser")
.WithMany("Logins")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
@ -234,7 +234,7 @@ namespace WebMVC.Migrations
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.WebMVC.Models.ApplicationUser")
b.HasOne("Microsoft.eShopOnContainers.WebMVC.ViewModels.ApplicationUser")
.WithMany("Roles")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);

View File

@ -1,6 +0,0 @@
FROM microsoft/aspnetcore:1.1
ARG source
WORKDIR /app
EXPOSE 80
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", "Identity.API.dll"]

View File

@ -64,7 +64,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
_orderItems = new List<OrderItemDTO>();
}
public CreateOrderCommand(string city, string street, string state, string country, string zipcode,
public CreateOrderCommand(string city, string street, string state, string country, string zipcode,
string cardNumber, string cardHolderName, DateTime cardExpiration,
string cardSecurityNumber, int cardTypeId) : this()
{

View File

@ -4,9 +4,23 @@
using Domain.AggregatesModel.OrderAggregate;
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories;
using System;
using System.Threading.Tasks;
public class CreateOrderCommandIdentifiedHandler : IdentifierCommandHandler<CreateOrderCommand, bool>
{
public CreateOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
{
}
protected override bool CreateResultForDuplicateRequest()
{
return true; // Ignore duplicate requests for creating order.
}
}
public class CreateOrderCommandHandler
: IAsyncRequestHandler<CreateOrderCommand, bool>
{

View File

@ -0,0 +1,20 @@
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
{
public class IdentifiedCommand<T, R> : IAsyncRequest<R>
where T : IAsyncRequest<R>
{
public T Command { get; }
public Guid Id { get; }
public IdentifiedCommand(T command, Guid id)
{
Command = command;
Id = id;
}
}
}

View File

@ -0,0 +1,60 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
{
/// <summary>
/// Provides a base implementation for handling duplicate request and ensuring idempotent updates, in the cases where
/// a requestid sent by client is used to detect duplicate requests.
/// </summary>
/// <typeparam name="T">Type of the command handler that performs the operation if request is not duplicated</typeparam>
/// <typeparam name="R">Return value of the inner command handler</typeparam>
public class IdentifierCommandHandler<T, R> : IAsyncRequestHandler<IdentifiedCommand<T, R>, R>
where T : IAsyncRequest<R>
{
private readonly IMediator _mediator;
private readonly IRequestManager _requestManager;
public IdentifierCommandHandler(IMediator mediator, IRequestManager requestManager)
{
_mediator = mediator;
_requestManager = requestManager;
}
/// <summary>
/// Creates the result value to return if a previous request was found
/// </summary>
/// <returns></returns>
protected virtual R CreateResultForDuplicateRequest()
{
return default(R);
}
/// <summary>
/// This method handles the command. It just ensures that no other request exists with the same ID, and if this is the case
/// just enqueues the original inner command.
/// </summary>
/// <param name="message">IdentifiedCommand which contains both original command & request ID</param>
/// <returns>Return value of inner command or default value if request same ID was found</returns>
public async Task<R> Handle(IdentifiedCommand<T, R> message)
{
var alreadyExists = await _requestManager.ExistAsync(message.Id);
if (alreadyExists)
{
return CreateResultForDuplicateRequest();
}
else
{
await _requestManager.CreateRequestForCommandAsync<T>(message.Id);
var result = await _mediator.SendAsync(message.Command);
return result;
}
}
}
}

View File

@ -28,9 +28,21 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[Route("new")]
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand createOrderCommand)
public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand createOrderCommand, [FromHeader(Name = "x-requestid")] string requestId)
{
var result = await _mediator.SendAsync(createOrderCommand);
bool result = false;
if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty)
{
var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(createOrderCommand, guid);
result = await _mediator.SendAsync(requestCreateOrder);
}
else
{
// If no x-requestid header is found we process the order anyway. This is just temporary to not break existing clients
// that aren't still updated. When all clients were updated this could be removed.
result = await _mediator.SendAsync(createOrderCommand);
}
if (result)
{
return Ok();

View File

@ -1,6 +0,0 @@
FROM microsoft/aspnetcore:1.1
ARG source
WORKDIR /app
EXPOSE 80
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", "Ordering.API.dll"]

View File

@ -33,6 +33,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
builder.RegisterType<OrderRepository>()
.As<IOrderRepository<Order>>()
.InstancePerLifetimeScope();
builder.RegisterType<RequestManager>()
.As<IRequestManager>()
.InstancePerLifetimeScope();
}
}
}

View File

@ -0,0 +1,245 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
namespace Ordering.API.Migrations
{
[DbContext(typeof(OrderingContext))]
[Migration("20170303085729_RequestsTable")]
partial class RequestsTable
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
.HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:Sequence:ordering.paymentseq", "'paymentseq', 'ordering', '1', '10', '', '', 'Int64', 'False'")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "buyerseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("IdentityGuid")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.HasIndex("IdentityGuid")
.IsUnique();
b.ToTable("buyers","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", b =>
{
b.Property<int>("Id")
.HasDefaultValue(1);
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.ToTable("cardtypes","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "paymentseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("Alias")
.IsRequired()
.HasMaxLength(200);
b.Property<int>("BuyerId");
b.Property<string>("CardHolderName")
.IsRequired()
.HasMaxLength(200);
b.Property<string>("CardNumber")
.IsRequired()
.HasMaxLength(25);
b.Property<int>("CardTypeId");
b.Property<DateTime>("Expiration");
b.HasKey("Id");
b.HasIndex("BuyerId");
b.HasIndex("CardTypeId");
b.ToTable("paymentmethods","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("City");
b.Property<string>("Country");
b.Property<string>("State");
b.Property<string>("Street");
b.Property<string>("ZipCode");
b.HasKey("Id");
b.ToTable("address","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "orderseq")
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int?>("AddressId");
b.Property<int>("BuyerId");
b.Property<DateTime>("OrderDate");
b.Property<int>("OrderStatusId");
b.Property<int>("PaymentMethodId");
b.HasKey("Id");
b.HasIndex("AddressId");
b.HasIndex("BuyerId");
b.HasIndex("OrderStatusId");
b.HasIndex("PaymentMethodId");
b.ToTable("orders","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "orderitemseq")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<decimal>("Discount");
b.Property<int>("OrderId");
b.Property<string>("PictureUrl");
b.Property<int>("ProductId");
b.Property<string>("ProductName")
.IsRequired();
b.Property<decimal>("UnitPrice");
b.Property<int>("Units");
b.HasKey("Id");
b.HasIndex("OrderId");
b.ToTable("orderItems","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", b =>
{
b.Property<int>("Id")
.HasDefaultValue(1);
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200);
b.HasKey("Id");
b.ToTable("orderstatus","ordering");
});
modelBuilder.Entity("Ordering.Infrastructure.ClientRequest", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired();
b.Property<DateTime>("Time");
b.HasKey("Id");
b.ToTable("requests","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")
.WithMany("PaymentMethods")
.HasForeignKey("BuyerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", "CardType")
.WithMany()
.HasForeignKey("CardTypeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", "Address")
.WithMany()
.HasForeignKey("AddressId");
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer")
.WithMany()
.HasForeignKey("BuyerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus")
.WithMany()
.HasForeignKey("OrderStatusId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", "PaymentMethod")
.WithMany()
.HasForeignKey("PaymentMethodId");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order")
.WithMany("OrderItems")
.HasForeignKey("OrderId")
.OnDelete(DeleteBehavior.Cascade);
});
}
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ordering.API.Migrations
{
public partial class RequestsTable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "requests",
schema: "ordering",
columns: table => new
{
Id = table.Column<Guid>(nullable: false),
Name = table.Column<string>(nullable: false),
Time = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_requests", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "requests",
schema: "ordering");
}
}
}

View File

@ -183,6 +183,21 @@ namespace Ordering.API.Migrations
b.ToTable("orderstatus","ordering");
});
modelBuilder.Entity("Ordering.Infrastructure.ClientRequest", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired();
b.Property<DateTime>("Time");
b.HasKey("Id");
b.ToTable("requests","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b =>
{
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer")

View File

@ -51,10 +51,11 @@
<PackageReference Include="System.Reflection" Version="4.3.0" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.0.1-rc3" />
<PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.1.0-preview4-final" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
</ItemGroup>
<ItemGroup>

View File

@ -29,7 +29,7 @@
if (env.IsDevelopment())
{
builder.AddUserSecrets();
builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly);
}
builder.AddEnvironmentVariables();
@ -67,7 +67,7 @@
Title = "Ordering HTTP API",
Version = "v1",
Description = "The Ordering Service HTTP API",
TermsOfService = "Terms Of Service"
TermsOfService = "Terms Of Service"
});
});
@ -82,7 +82,7 @@
// Add application services.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IIdentityService,IdentityService>();
services.AddTransient<IIdentityService, IdentityService>();
services.AddOptions();
@ -92,7 +92,7 @@
container.Populate(services);
container.RegisterModule(new MediatorModule());
container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"] ));
container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"]));
return new AutofacServiceProvider(container.Build());
}

View File

@ -22,6 +22,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
public OrderStatus OrderStatus { get; private set; }
private int _orderStatusId;
// DDD Patterns comment
// Using a private collection field, better for DDD Aggregate's encapsulation
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
@ -46,7 +47,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
_paymentMethodId = paymentMethodId;
_orderStatusId = OrderStatus.InProcess.Id;
_orderDate = DateTime.UtcNow;
Address = address;
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
{
public class ClientRequest
{
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime Time { get; set; }
}
}

View File

@ -30,6 +30,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ClientRequest>(ConfigureRequests);
modelBuilder.Entity<Address>(ConfigureAddress);
modelBuilder.Entity<PaymentMethod>(ConfigurePayment);
modelBuilder.Entity<Order>(ConfigureOrder);
@ -39,6 +41,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
modelBuilder.Entity<Buyer>(ConfigureBuyer);
}
private void ConfigureRequests(EntityTypeBuilder<ClientRequest> requestConfiguration)
{
requestConfiguration.ToTable("requests", DEFAULT_SCHEMA);
requestConfiguration.HasKey(cr => cr.Id);
requestConfiguration.Property(cr => cr.Name).IsRequired();
requestConfiguration.Property(cr => cr.Time).IsRequired();
}
void ConfigureAddress(EntityTypeBuilder<Address> addressConfiguration)
{
addressConfiguration.ToTable("address", DEFAULT_SCHEMA);

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
public interface IRequestManager
{
Task<bool> ExistAsync(Guid id);
Task CreateRequestForCommandAsync<T>(Guid id);
}
}

View File

@ -0,0 +1,42 @@
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
public class RequestManager : IRequestManager
{
private readonly OrderingContext _context;
public RequestManager(OrderingContext ctx)
{
_context = ctx;
}
public async Task<bool> ExistAsync(Guid id)
{
var request = await _context.FindAsync<ClientRequest>(id);
return request != null;
}
public async Task CreateRequestForCommandAsync<T>(Guid id)
{
var exists = await ExistAsync(id);
var request = exists ?
throw new Exception($"Request with {id} already exists") :
new ClientRequest()
{
Id = id,
Name = typeof(T).Name,
Time = DateTime.UtcNow
};
_context.Add(request);
await _context.SaveChangesAsync();
}
}
}

View File

@ -1,7 +1,7 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.AspNetCore.Http.Authentication;

View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;

View File

@ -3,9 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.WebMVC.Models.Pagination;
using Microsoft.eShopOnContainers.WebMVC.ViewModels.Pagination;
using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.eShopOnContainers.WebMVC.Models.CatalogViewModels;
using Microsoft.eShopOnContainers.WebMVC.ViewModels.CatalogViewModels;
namespace Microsoft.eShopOnContainers.WebMVC.Controllers
@ -33,7 +33,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
PaginationInfo = new PaginationInfo()
{
ActualPage = page ?? 0,
ItemsPerPage = (catalog.Count < itemsPage) ? catalog.Count : itemsPage,
ItemsPerPage = catalog.Data.Count,
TotalItems = catalog.Count,
TotalPages = int.Parse(Math.Ceiling(((decimal)catalog.Count / itemsPage)).ToString())
}

View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.WebMVC.Services;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.AspNetCore.Authorization;
using System.Net.Http;

View File

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.AspNetCore.Http;
using System.Net.Http;
using Microsoft.Extensions.Options;

View File

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.CodeAnalysis.Options;
using Microsoft.Extensions.Options;
using System.Net.Http;

View File

@ -1,4 +1,4 @@
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;

View File

@ -1,5 +1,5 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;

View File

@ -1,4 +1,4 @@
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;

View File

@ -1,4 +1,4 @@
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;

View File

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System.Net.Http;
@ -79,14 +79,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
_apiClient = new HttpClient();
_apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
_apiClient.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString());
var ordersUrl = $"{_remoteServiceBaseUrl}/new";
order.CardTypeId = 1;
order.CardExpirationApiFormat();
SetFakeIdToProducts(order);
StringContent content = new StringContent(JsonConvert.SerializeObject(order), System.Text.Encoding.UTF8, "application/json");
var response = await _apiClient.PostAsync(ordersUrl, content);
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)

View File

@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.eShopOnContainers.WebMVC.Services;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;

View File

@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.Models.CartViewModels;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.eShopOnContainers.WebMVC.ViewModels.CartViewModels;
using Microsoft.eShopOnContainers.WebMVC.Services;
using System;
using System.Collections.Generic;

View File

@ -1,5 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.WebMVC.Models;
using Microsoft.eShopOnContainers.WebMVC.ViewModels;
using Microsoft.eShopOnContainers.WebMVC.Services;
using System;
using System.Collections.Generic;

View File

@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Models.Annotations
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Annotations
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class CardExpirationAttribute : ValidationAttribute

View File

@ -5,7 +5,7 @@ using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
namespace Microsoft.eShopOnContainers.WebMVC.Models
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Models
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{
public class Basket
{

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Models
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{
public class BasketItem
{

View File

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Models.CartViewModels
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.CartViewModels
{
public class CartComponentViewModel
{

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Models
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{
public class Catalog
{

View File

@ -1,6 +1,6 @@
using System;
namespace Microsoft.eShopOnContainers.WebMVC.Models
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{
public class CatalogItem
{

View File

@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.eShopOnContainers.WebMVC.Models.Pagination;
using Microsoft.eShopOnContainers.WebMVC.ViewModels.Pagination;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Models.CatalogViewModels
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.CatalogViewModels
{
public class IndexViewModel
{

View File

@ -1,6 +1,6 @@
using System;
namespace Microsoft.eShopOnContainers.WebMVC.Models
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{
public class Header
{

View File

@ -1,4 +1,4 @@
using Microsoft.eShopOnContainers.WebMVC.Models.Annotations;
using Microsoft.eShopOnContainers.WebMVC.ViewModels.Annotations;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@ -7,7 +7,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Models
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{
public class Order
{
@ -55,6 +55,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Models
public List<OrderItem> OrderItems { get; }
[Required]
public Guid RequestId { get; set; }
public void CardExpirationShortFormat()
{
CardExpirationShort = CardExpiration.ToString("MM/yy");

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.Models
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{
public class OrderItem
{

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Pagination
{
public class PaginationInfo
{
public int TotalItems { get; set; }
public int ItemsPerPage { get; set; }
public int ActualPage { get; set; }
public int TotalPages { get; set; }
public string Previous { get; set; }
public string Next { get; set; }
}
}

View File

@ -1,7 +1,7 @@
@using Microsoft.eShopOnContainers.WebMVC.Services
@using Microsoft.eShopOnContainers.WebMVC.Models
@using Microsoft.eShopOnContainers.WebMVC.ViewModels
@model Microsoft.eShopOnContainers.WebMVC.Models.Basket
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Basket
@inject IIdentityParser<ApplicationUser> UserManager
@{

View File

@ -1,6 +1,6 @@
@{
ViewData["Title"] = "Catalog";
@model Microsoft.eShopOnContainers.WebMVC.Models.CatalogViewModels.IndexViewModel
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.CatalogViewModels.IndexViewModel
}
<section class="esh-catalog-hero">
<div class="container">

View File

@ -1,24 +1,28 @@
@model Microsoft.eShopOnContainers.WebMVC.Models.Pagination.PaginationInfo
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Pagination.PaginationInfo
<div class="esh-pager">
<div class="container">
<article class="esh-pager-wrapper row">
<nav>
<a class="esh-pager-item esh-pager-item--navigable @Model.Previous"
id="Previous"
href="@Url.Action("Index","Catalog", new { page = Model.ActualPage -1 })"
aria-label="Previous">
id="Previous"
asp-controller="Catalog"
asp-action="Index"
asp-route-page="@(Model.ActualPage -1)"
aria-label="Previous">
Previous
</a>
<span class="esh-pager-item">
Showing @Html.DisplayFor(modelItem => modelItem.ItemsPerPage) of @Html.DisplayFor(modelItem => modelItem.TotalItems) products - Page @(Model.ActualPage + 1) - @Html.DisplayFor(x => x.TotalPages)
Showing @Model.ItemsPerPage of @Model.TotalItems products - Page @(Model.ActualPage + 1) - @Model.TotalPages
</span>
<a class="esh-pager-item esh-pager-item--navigable @Model.Next"
id="Next"
href="@Url.Action("Index","Catalog", new { page = Model.ActualPage + 1 })"
aria-label="Next">
id="Next"
asp-controller="Catalog"
asp-action="Index"
asp-route-page="@(Model.ActualPage + 1)"
aria-label="Next">
Next
</a>
</nav>

View File

@ -1,5 +1,5 @@
@using Microsoft.eShopOnContainers.WebMVC.Services
@model Microsoft.eShopOnContainers.WebMVC.Models.Order
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Order
@inject IIdentityParser<ApplicationUser> UserManager
@{
@ -89,6 +89,7 @@
</div>
</section>
<input asp-for="ZipCode" type="hidden" />
<input asp-for="RequestId" type="hidden" value="@Guid.NewGuid().ToString()"/>
</form>
</div>

View File

@ -1,6 +1,6 @@
@using Microsoft.eShopOnContainers.WebMVC.Models
@using Microsoft.eShopOnContainers.WebMVC.ViewModels
@model Microsoft.eShopOnContainers.WebMVC.Models.Order
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Order
@{
ViewData["Title"] = "Order Detail";

View File

@ -1,6 +1,6 @@
@using Microsoft.eShopOnContainers.WebMVC.Models
@using Microsoft.eShopOnContainers.WebMVC.ViewModels
@model IEnumerable<Microsoft.eShopOnContainers.WebMVC.Models.Order>
@model IEnumerable<Microsoft.eShopOnContainers.WebMVC.ViewModels.Order>
@{
ViewData["Title"] = "My Orders";

View File

@ -1,4 +1,4 @@
@model Microsoft.eShopOnContainers.WebMVC.Models.Order
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Order
<section class="esh-orders_new-section">

View File

@ -1,4 +1,4 @@
@model Microsoft.eShopOnContainers.WebMVC.Models.CartViewModels.CartComponentViewModel
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.CartViewModels.CartComponentViewModel
@{
ViewData["Title"] = "My Cart";

View File

@ -1,4 +1,4 @@
@model Microsoft.eShopOnContainers.WebMVC.Models.Basket
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Basket
@{
ViewData["Title"] = "My Cart";

View File

@ -1,4 +1,4 @@
@model Microsoft.eShopOnContainers.WebMVC.Models.Header
@model Microsoft.eShopOnContainers.WebMVC.ViewModels.Header
<div class="esh-header">
<div class="container">

View File

@ -1,5 +1,5 @@
@using Microsoft.AspNetCore.Identity
@using Microsoft.eShopOnContainers.WebMVC.Models
@using Microsoft.eShopOnContainers.WebMVC.ViewModels
@using Microsoft.eShopOnContainers.WebMVC.Services
@inject IIdentityParser<ApplicationUser> UserManager

View File

@ -1,5 +1,5 @@
@using Microsoft.eShopOnContainers.WebMVC
@using Microsoft.eShopOnContainers.WebMVC.Models
@using Microsoft.eShopOnContainers.WebMVC.ViewModels
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,9 @@
version: '2'
services:
ci-build:
image: microsoft/aspnetcore-build:1.0-1.1
volumes:
- .:/src
working_dir: /src
command: /bin/bash -c "dotnet restore ./eShopWeb.sln && dotnet publish ./eShopWeb.sln -c Release -o ./obj/Docker/publish"

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectGuid>91cd7b6e-a849-48c1-b058-4bc47c4cd978</ProjectGuid>
<DockerLaunchBrowser>True</DockerLaunchBrowser>
<DockerServiceUrl>http://localhost:{ServicePort}</DockerServiceUrl>
<DockerServiceName>eshopweb</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.ci.build.yml" />
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.vs.debug.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.vs.release.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.yml" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
version: '2'
services:
eshopweb:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word
- CatalogBaseUrl=http://localhost:5106
ports:
- "5106:5106"
sql.data:
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5433:1433"

View File

@ -0,0 +1,17 @@
version: '2'
services:
eshopweb:
image: eshop/web:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
environment:
- DOTNET_USE_POLLING_FILE_WATCHER=1
volumes:
- ./eShopWeb:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"

View File

@ -0,0 +1,12 @@
version: '2'
services:
eshopweb:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"

View File

@ -0,0 +1,12 @@
version: '2'
services:
eshopweb:
image: eshop/web
build:
context: ./eShopWeb
dockerfile: Dockerfile
depends_on:
- sql.data
sql.data:
image: microsoft/mssql-server-linux

View File

@ -0,0 +1,48 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eShopWeb", "eShopWeb\eShopWeb.csproj", "{CA5B730B-7195-4E29-B030-A2007E004B98}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{91CD7B6E-A849-48C1-B058-4BC47C4CD978}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CA5B730B-7195-4E29-B030-A2007E004B98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA5B730B-7195-4E29-B030-A2007E004B98}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA5B730B-7195-4E29-B030-A2007E004B98}.Debug|x64.ActiveCfg = Debug|Any CPU
{CA5B730B-7195-4E29-B030-A2007E004B98}.Debug|x64.Build.0 = Debug|Any CPU
{CA5B730B-7195-4E29-B030-A2007E004B98}.Debug|x86.ActiveCfg = Debug|Any CPU
{CA5B730B-7195-4E29-B030-A2007E004B98}.Debug|x86.Build.0 = Debug|Any CPU
{CA5B730B-7195-4E29-B030-A2007E004B98}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA5B730B-7195-4E29-B030-A2007E004B98}.Release|Any CPU.Build.0 = Release|Any CPU
{CA5B730B-7195-4E29-B030-A2007E004B98}.Release|x64.ActiveCfg = Release|Any CPU
{CA5B730B-7195-4E29-B030-A2007E004B98}.Release|x64.Build.0 = Release|Any CPU
{CA5B730B-7195-4E29-B030-A2007E004B98}.Release|x86.ActiveCfg = Release|Any CPU
{CA5B730B-7195-4E29-B030-A2007E004B98}.Release|x86.Build.0 = Release|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Debug|x64.ActiveCfg = Debug|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Debug|x64.Build.0 = Debug|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Debug|x86.ActiveCfg = Debug|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Debug|x86.Build.0 = Debug|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Release|Any CPU.Build.0 = Release|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Release|x64.ActiveCfg = Release|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Release|x64.Build.0 = Release|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Release|x86.ActiveCfg = Release|Any CPU
{91CD7B6E-A849-48C1-B058-4BC47C4CD978}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,3 @@
{
"directory": "wwwroot/lib"
}

View File

@ -0,0 +1,3 @@
*
!obj/Docker/publish/*
!obj/Docker/empty/

View File

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

View File

@ -0,0 +1,70 @@
using Microsoft.eShopWeb.Services;
using Microsoft.eShopWeb.ViewModels;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using System;
using System.IO;
using System.Threading.Tasks;
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace Microsoft.eShopWeb.Controllers
{
public class CatalogController : Controller
{
private readonly IHostingEnvironment _env;
private readonly ICatalogService _catalogSvc;
public CatalogController(IHostingEnvironment env, ICatalogService catalogSvc)
{
_env = env;
_catalogSvc = catalogSvc;
}
// GET: /<controller>/
public async Task<IActionResult> Index(int? BrandFilterApplied, int? TypesFilterApplied, int? page)
{
var itemsPage = 10;
var catalog = await _catalogSvc.GetCatalogItems(page ?? 0, itemsPage, BrandFilterApplied, TypesFilterApplied);
var vm = new CatalogIndex()
{
CatalogItems = catalog.Data,
Brands = await _catalogSvc.GetBrands(),
Types = await _catalogSvc.GetTypes(),
BrandFilterApplied = BrandFilterApplied ?? 0,
TypesFilterApplied = TypesFilterApplied ?? 0,
PaginationInfo = new PaginationInfo()
{
ActualPage = page ?? 0,
ItemsPerPage = catalog.Data.Count,
TotalItems = catalog.Count,
TotalPages = int.Parse(Math.Ceiling(((decimal)catalog.Count / itemsPage)).ToString())
}
};
vm.PaginationInfo.Next = (vm.PaginationInfo.ActualPage == vm.PaginationInfo.TotalPages - 1) ? "is-disabled" : "";
vm.PaginationInfo.Previous = (vm.PaginationInfo.ActualPage == 0) ? "is-disabled" : "";
return View(vm);
}
[HttpGet("{id}")]
[Route("[controller]/pic/{id}")]
// GET: /<controller>/pic/{id}
public IActionResult GetImage(int id)
{
var contentRoot = _env.ContentRootPath + "//Pics";
var path = Path.Combine(contentRoot, id + ".png");
Byte[] b = System.IO.File.ReadAllBytes(path);
return File(b, "image/png");
}
public IActionResult Error()
{
return View();
}
}
}

View File

@ -3,4 +3,4 @@ ARG source
WORKDIR /app
EXPOSE 80
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", "WebMVC.dll"]
ENTRYPOINT ["dotnet", "eShopWeb.dll"]

View File

@ -0,0 +1,79 @@
namespace Microsoft.eShopWeb.Infrastructure
{
using eShopWeb.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class CatalogContext : DbContext
{
public CatalogContext(DbContextOptions options) : base(options)
{
}
public DbSet<CatalogItem> CatalogItems { get; set; }
public DbSet<CatalogBrand> CatalogBrands { get; set; }
public DbSet<CatalogType> CatalogTypes { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<CatalogBrand>(ConfigureCatalogBrand);
builder.Entity<CatalogType>(ConfigureCatalogType);
builder.Entity<CatalogItem>(ConfigureCatalogItem);
}
void ConfigureCatalogItem(EntityTypeBuilder<CatalogItem> builder)
{
builder.ToTable("Catalog");
builder.Property(ci => ci.Id)
.ForSqlServerUseSequenceHiLo("catalog_hilo")
.IsRequired();
builder.Property(ci => ci.Name)
.IsRequired(true)
.HasMaxLength(50);
builder.Property(ci => ci.Price)
.IsRequired(true);
builder.Property(ci => ci.PictureUri)
.IsRequired(false);
builder.HasOne(ci => ci.CatalogBrand)
.WithMany()
.HasForeignKey(ci => ci.CatalogBrandId);
builder.HasOne(ci => ci.CatalogType)
.WithMany()
.HasForeignKey(ci => ci.CatalogTypeId);
}
void ConfigureCatalogBrand(EntityTypeBuilder<CatalogBrand> builder)
{
builder.ToTable("CatalogBrand");
builder.HasKey(ci => ci.Id);
builder.Property(ci => ci.Id)
.ForSqlServerUseSequenceHiLo("catalog_brand_hilo")
.IsRequired();
builder.Property(cb => cb.Brand)
.IsRequired()
.HasMaxLength(100);
}
void ConfigureCatalogType(EntityTypeBuilder<CatalogType> builder)
{
builder.ToTable("CatalogType");
builder.HasKey(ci => ci.Id);
builder.Property(ci => ci.Id)
.ForSqlServerUseSequenceHiLo("catalog_type_hilo")
.IsRequired();
builder.Property(cb => cb.Type)
.IsRequired()
.HasMaxLength(100);
}
}
}

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