From 28b4ccb317080b840f8b0273274a7e1202b3a2af Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Wed, 14 Sep 2016 00:44:13 -0700 Subject: [PATCH] Working Ordering microservice on Docker with Docker-compose deployment --- eShopOnContainers.sln | 10 + src/Console/eShopConsole/.dockerignore | 5 + src/Console/eShopConsole/DockerTask.ps1 | 392 ++++++++++++++ src/Console/eShopConsole/Dockerfile | 10 + src/Console/eShopConsole/Dockerfile.debug | 20 + src/Console/eShopConsole/Program.cs | 81 +++ .../eShopConsole/Properties/AssemblyInfo.cs | 19 + .../eShopConsole/Properties/Docker.props | 17 + .../eShopConsole/Properties/Docker.targets | 75 +++ .../Properties/launchSettings.json | 8 + .../eShopConsole/docker-compose.debug.yml | 12 + src/Console/eShopConsole/docker-compose.yml | 8 + src/Console/eShopConsole/eShopConsole.xproj | 22 + src/Console/eShopConsole/project.json | 31 ++ .../Ordering/Ordering.API/.dockerignore | 5 + .../Controllers/EnvironmentInfoController.cs | 23 + .../Controllers/OrderingController.cs | 72 ++- .../Ordering/Ordering.API/DockerTask.ps1 | 489 ++++++++++++++++++ src/Services/Ordering/Ordering.API/Dockerfile | 16 +- .../Ordering/Ordering.API/Dockerfile.debug | 24 + ...0160909202620_MyFirstMigration.Designer.cs | 37 -- .../20160909202620_MyFirstMigration.cs | 32 -- .../20160909223213_Migration2.Designer.cs | 84 --- .../Migrations/20160909223213_Migration2.cs | 98 ---- .../20160909233852_Migration3.Designer.cs | 84 --- .../Migrations/20160909233852_Migration3.cs | 19 - .../20160913052800_Migration4.Designer.cs | 89 ---- .../Migrations/20160913052800_Migration4.cs | 37 -- .../Migrations/20160913061710_Migration5.cs | 46 -- ... => 20160913204939_Migration1.Designer.cs} | 4 +- .../Migrations/20160913204939_Migration1.cs | 123 +++++ ...t.cs => OrderingDbContextModelSnapshot.cs} | 2 +- .../Ordering/Ordering.API/Ordering.API.xproj | 3 + .../Ordering.API/Properties/Docker.props | 17 + .../Ordering.API/Properties/Docker.targets | 75 +++ .../Properties/launchSettings.json | 8 +- src/Services/Ordering/Ordering.API/Startup.cs | 25 +- .../Ordering.API/docker-compose.debug.yml | 14 + .../Ordering/Ordering.API/docker-compose.yml | 10 + .../Ordering/Ordering.API/project.json | 25 +- .../AggregatesModel/Buyer/Buyer.cs | 2 +- .../AggregatesModel/Order/Order.cs | 2 +- .../IBuyerRepository.cs | 4 +- .../IOrderRepository.cs | 14 +- .../SeedWork/IAggregateRoot.cs | 6 + .../Ordering.Domain/SeedWork/IRepository.cs | 28 +- .../{AggregateRoot.cs => IUnitOfWork.cs} | 5 +- .../Queries/IOrderdingQueries.cs | 24 + .../Queries/OrderingQueries.cs | 9 +- .../Repositories/OrderRepository.cs | 45 +- .../Ordering.SqlData/SeedWork/Repository.cs | 82 --- .../UnitOfWork/DBContextUtil.cs | 14 +- .../UnitOfWork/OrderingDbContext.cs | 37 +- .../Ordering.Test/DataIntegrationTests.cs | 18 +- 54 files changed, 1733 insertions(+), 728 deletions(-) create mode 100644 src/Console/eShopConsole/.dockerignore create mode 100644 src/Console/eShopConsole/DockerTask.ps1 create mode 100644 src/Console/eShopConsole/Dockerfile create mode 100644 src/Console/eShopConsole/Dockerfile.debug create mode 100644 src/Console/eShopConsole/Program.cs create mode 100644 src/Console/eShopConsole/Properties/AssemblyInfo.cs create mode 100644 src/Console/eShopConsole/Properties/Docker.props create mode 100644 src/Console/eShopConsole/Properties/Docker.targets create mode 100644 src/Console/eShopConsole/Properties/launchSettings.json create mode 100644 src/Console/eShopConsole/docker-compose.debug.yml create mode 100644 src/Console/eShopConsole/docker-compose.yml create mode 100644 src/Console/eShopConsole/eShopConsole.xproj create mode 100644 src/Console/eShopConsole/project.json create mode 100644 src/Services/Ordering/Ordering.API/.dockerignore create mode 100644 src/Services/Ordering/Ordering.API/Controllers/EnvironmentInfoController.cs create mode 100644 src/Services/Ordering/Ordering.API/DockerTask.ps1 create mode 100644 src/Services/Ordering/Ordering.API/Dockerfile.debug delete mode 100644 src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.Designer.cs delete mode 100644 src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.cs delete mode 100644 src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.Designer.cs delete mode 100644 src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.cs delete mode 100644 src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.Designer.cs delete mode 100644 src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.cs delete mode 100644 src/Services/Ordering/Ordering.API/Migrations/20160913052800_Migration4.Designer.cs delete mode 100644 src/Services/Ordering/Ordering.API/Migrations/20160913052800_Migration4.cs delete mode 100644 src/Services/Ordering/Ordering.API/Migrations/20160913061710_Migration5.cs rename src/Services/Ordering/Ordering.API/Migrations/{20160913061710_Migration5.Designer.cs => 20160913204939_Migration1.Designer.cs} (98%) create mode 100644 src/Services/Ordering/Ordering.API/Migrations/20160913204939_Migration1.cs rename src/Services/Ordering/Ordering.API/Migrations/{OrderingContextModelSnapshot.cs => OrderingDbContextModelSnapshot.cs} (98%) create mode 100644 src/Services/Ordering/Ordering.API/Properties/Docker.props create mode 100644 src/Services/Ordering/Ordering.API/Properties/Docker.targets create mode 100644 src/Services/Ordering/Ordering.API/docker-compose.debug.yml create mode 100644 src/Services/Ordering/Ordering.API/docker-compose.yml rename src/Services/Ordering/Ordering.Domain/{RepositoryContracts => Contracts}/IBuyerRepository.cs (69%) rename src/Services/Ordering/Ordering.Domain/{RepositoryContracts => Contracts}/IOrderRepository.cs (50%) create mode 100644 src/Services/Ordering/Ordering.Domain/SeedWork/IAggregateRoot.cs rename src/Services/Ordering/Ordering.Domain/SeedWork/{AggregateRoot.cs => IUnitOfWork.cs} (59%) create mode 100644 src/Services/Ordering/Ordering.SqlData/Queries/IOrderdingQueries.cs delete mode 100644 src/Services/Ordering/Ordering.SqlData/SeedWork/Repository.cs diff --git a/eShopOnContainers.sln b/eShopOnContainers.sln index 0b78e08a1..4f2b08979 100644 --- a/eShopOnContainers.sln +++ b/eShopOnContainers.sln @@ -51,6 +51,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{EF EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Ordering.Test", "test\Services\Ordering.Test\Ordering.Test.xproj", "{A0AFC432-3846-4B4E-BD8E-3C8C896F4967}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Console App", "Console App", "{48FC45C5-223F-4B59-AC77-6CBB1C561E85}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "eShopConsole", "src\Console\eShopConsole\eShopConsole.xproj", "{C10C7B69-CE4F-4167-928E-33B7FA1DFFC7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -101,6 +105,10 @@ Global {A0AFC432-3846-4B4E-BD8E-3C8C896F4967}.Debug|Any CPU.Build.0 = Debug|Any CPU {A0AFC432-3846-4B4E-BD8E-3C8C896F4967}.Release|Any CPU.ActiveCfg = Release|Any CPU {A0AFC432-3846-4B4E-BD8E-3C8C896F4967}.Release|Any CPU.Build.0 = Release|Any CPU + {C10C7B69-CE4F-4167-928E-33B7FA1DFFC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C10C7B69-CE4F-4167-928E-33B7FA1DFFC7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C10C7B69-CE4F-4167-928E-33B7FA1DFFC7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C10C7B69-CE4F-4167-928E-33B7FA1DFFC7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -124,5 +132,7 @@ Global {5DA6EF13-C7E0-4EC4-8599-A61C6AC2628D} = {0BD0DB92-2D98-44D9-9AC0-C59186D59B0B} {EF0337F2-ED00-4643-89FD-EE10863F1870} = {A857AD10-40FF-4303-BEC2-FF1C58D5735E} {A0AFC432-3846-4B4E-BD8E-3C8C896F4967} = {EF0337F2-ED00-4643-89FD-EE10863F1870} + {48FC45C5-223F-4B59-AC77-6CBB1C561E85} = {932D8224-11F6-4D07-B109-DA28AD288A63} + {C10C7B69-CE4F-4167-928E-33B7FA1DFFC7} = {48FC45C5-223F-4B59-AC77-6CBB1C561E85} EndGlobalSection EndGlobal diff --git a/src/Console/eShopConsole/.dockerignore b/src/Console/eShopConsole/.dockerignore new file mode 100644 index 000000000..098ccd44a --- /dev/null +++ b/src/Console/eShopConsole/.dockerignore @@ -0,0 +1,5 @@ +# Docker support files +docker-compose.yml +docker-compose.debug.yml +Dockerfile +Dockerfile.debug diff --git a/src/Console/eShopConsole/DockerTask.ps1 b/src/Console/eShopConsole/DockerTask.ps1 new file mode 100644 index 000000000..e0fa2c5ca --- /dev/null +++ b/src/Console/eShopConsole/DockerTask.ps1 @@ -0,0 +1,392 @@ +<# +.SYNOPSIS +Deploys an ASP .NET Core Web Application into a docker container running in a specified Docker machine. + +.DESCRIPTION +The following script will execute a set of Docker commands against the designated dockermachine. + +.PARAMETER Build +Builds the containers using docker-compose build. + +.PARAMETER Clean +Clears out any running containers (docker-compose kill, docker-compose rm -f). + +.PARAMETER Exec +Executes a command in the container using docker exec. + +.PARAMETER Refresh +Kills the running command in the container, publishes the project and restarts executing the command. + +.PARAMETER Run +Removes any conflicting containers running on the same port, then instances the containers using docker-compose up. + +.PARAMETER Environment +Specifies the configuration under which the project will be built and run (Debug or Release). + +.PARAMETER Machine +Specifies the docker machine name to connect to. This is optional and if left blank or not provided it will use the currently configured docker host, or if no host is set, will use the Docker for Windows beta. + +.PARAMETER ProjectFolder +Specifies the project folder, defaults to the parent of the directory containing this script. + +.PARAMETER ProjectName +Specifies the project name used by docker-compose, defaults to the name of $ProjectFolder. + +.PARAMETER NoCache +Specifies the build argument --no-cache. + +.PARAMETER RemoteDebugging +Specifies if remote debugging is needed, defaults to $False. + +.PARAMETER ClrDebugVersion +Specifies the version of the debugger, defaults to 'VS2015U2'. + +.PARAMETER Command +Specifies the command to run in the container. + +.INPUTS +None. You cannot pipe inputs to DockerTask. + +.EXAMPLE +Compiles the project and builds the docker image using the currently configured docker host, or when no host is set, used for the Docker for Windows beta. To see the container running, use the -Run parameter +C:\PS> .\DockerTask.ps1 -Build -Environment Release + +.EXAMPLE +Will use 'docker-compose up' on the project, using the docker-machine instance named 'default', and opens a browser once the container responds. Assumes -Build was previously run. For the Docker for Windows Beta, remove the -Machine parameter or pass '' as the value. +C:\PS> .\DockerTask.ps1 -Run -Environment Release -Machine 'default' + +.LINK +http://aka.ms/DockerToolsForVS +#> + +Param( + [Parameter(ParameterSetName = "Build", Position = 0, Mandatory = $True)] + [switch]$Build, + [Parameter(ParameterSetName = "Clean", Position = 0, Mandatory = $True)] + [switch]$Clean, + [Parameter(ParameterSetName = "Run", Position = 0, Mandatory = $True)] + [switch]$Run, + [Parameter(ParameterSetName = "Exec", Position = 0, Mandatory = $True)] + [switch]$Exec, + [Parameter(ParameterSetName = "Refresh", Position = 0, Mandatory = $True)] + [switch]$Refresh, + [Parameter(ParameterSetName = "ValidateVolumeMapping", Position = 0, Mandatory = $True)] + [switch]$ValidateVolumeMapping, + [parameter(ParameterSetName = "Clean", Position = 1, Mandatory = $True)] + [parameter(ParameterSetName = "Build", Position = 1, Mandatory = $True)] + [parameter(ParameterSetName = "Run", Position = 1, Mandatory = $True)] + [parameter(ParameterSetName = "Refresh", Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String]$Environment, + [parameter(ParameterSetName = "Clean", Position = 2, Mandatory = $False)] + [parameter(ParameterSetName = "Build", Position = 2, Mandatory = $False)] + [parameter(ParameterSetName = "Run", Position = 2, Mandatory = $False)] + [parameter(ParameterSetName = "Exec", Position = 1, Mandatory = $False)] + [parameter(ParameterSetName = "Refresh", Position = 2, Mandatory = $False)] + [parameter(ParameterSetName = "ValidateVolumeMapping", Position = 1, Mandatory = $False)] + [String]$Machine, + [parameter(ParameterSetName = "Clean", Position = 3, Mandatory = $False)] + [parameter(ParameterSetName = "Build", Position = 3, Mandatory = $False)] + [parameter(ParameterSetName = "Run", Position = 3, Mandatory = $False)] + [parameter(ParameterSetName = "Exec", Position = 2, Mandatory = $False)] + [parameter(ParameterSetName = "Refresh", Position = 3, Mandatory = $False)] + [ValidateNotNullOrEmpty()] + [String]$ProjectFolder = (Split-Path -Path $MyInvocation.MyCommand.Definition), + [parameter(ParameterSetName = "Clean", Position = 4, Mandatory = $False)] + [parameter(ParameterSetName = "Build", Position = 4, Mandatory = $False)] + [parameter(ParameterSetName = "Run", Position = 4, Mandatory = $False)] + [parameter(ParameterSetName = "Exec", Position = 3, Mandatory = $False)] + [parameter(ParameterSetName = "Refresh", Position = 4, Mandatory = $False)] + [ValidateNotNullOrEmpty()] + [String]$ProjectName = (Split-Path -Path (Resolve-Path $ProjectFolder) -Leaf).ToLowerInvariant(), + [parameter(ParameterSetName = "Build", Position = 5, Mandatory = $False)] + [switch]$NoCache, + [parameter(ParameterSetName = "Run", Position = 6, Mandatory = $False)] + [bool]$RemoteDebugging = $False, + [parameter(ParameterSetName = "Build", Position = 6, Mandatory = $False)] + [String]$ClrDebugVersion = "VS2015U2", + [parameter(ParameterSetName = "Exec", Position = 4, Mandatory = $True)] + [parameter(ParameterSetName = "Refresh", Position = 5, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String]$Command +) + +$ErrorActionPreference = "Stop" + +# Turns VERBOSE output ON +$VerbosePreference = "Continue" + +# Docker Working Directory for validating volume mapping. Should be in sync with Dockerfile and Docker.props. +$DockerWorkingDirectory = "/app/" + +# Path for the launch URL to be opened +$launchURLPath = "" + +# The project name can only contain alphanumeric charecters, replace everything else with empty string +$ProjectName = $ProjectName -replace "[^a-zA-Z0-9]", "" + +# The name of the image created by the compose file +$ImageName = "username/eshopconsole" + +# Calculate the name of the container created by the compose file +$ContainerName = "${ProjectName}_eshopconsole" + +# .net core runtime ID for the container (used to publish the app correctly) +$RuntimeID = "debian.8-x64" +# .net core framework (used to publish the app correctly) +$Framework = "netcoreapp1.0" + +# Kills all containers using an image, removes all containers using an image, and removes the image. +function Clean () { + $composeFilePath = GetComposeFilePath($ProjectFolder) + + # Call compose-down to clean up the containers + $shellCommand = "docker-compose -f '$composeFilePath' -p $ProjectName down" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression "cmd /c $shellCommand `"2>&1`"" + if ($LastExitCode -ne 0) { + Write-Error "Failed to clean up the containers" + } + + # If $ImageName exists remove it + $ImageNameRegEx = "\b$ImageName\b" + docker images | select-string -pattern $ImageNameRegEx | foreach { + $imageName = $_.Line.split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)[0]; + $tag = $_.Line.split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)[1]; + $shellCommand = "docker rmi -f ${imageName}:$tag" + Write-Verbose "Executing: $shellCommand"; + Invoke-Expression "cmd /c $shellCommand `"2>&1`"" + } + + # Remove any dangling images (from previous builds) + $shellCommand = "docker images -q --filter 'dangling=true'" + Write-Verbose "Executing: $shellCommand" + $danglingImages = $(Invoke-Expression "cmd /c $shellCommand `"2>&1`"") + if (-not [String]::IsNullOrWhiteSpace($danglingImages)) { + $shellCommand = "docker rmi -f $danglingImages" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression "cmd /c $shellCommand `"2>&1`"" + } + + # If the folder for publishing exists, delete it + if (Test-Path $pubPath) { + Remove-Item $pubPath -Force -Recurse + } +} + +# Runs docker build. +function Build () { + # Publish the project + PublishProject + + $composeFilePath = GetComposeFilePath($pubPath) + + $buildArgs = "" + if ($NoCache) + { + $buildArgs = "--no-cache" + } + + # Call docker-compose on the published project to build the images + $shellCommand = "docker-compose -f '$composeFilePath' -p $ProjectName build $buildArgs" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression "cmd /c $shellCommand `"2>&1`"" + if ($LastExitCode -ne 0) { + Write-Error "Failed to build the image" + } +} + +function GetContainerId () { + $containerId = (docker ps -f "name=$ContainerName" -q -n=1) + if ([System.String]::IsNullOrWhiteSpace($containerId)) { + Write-Error "Could not find a container named $ContainerName" + } + + $containerId +} + +# Validates volume mapping +function ValidateVolumeMapping () { + # Volume mapping enables shared folder mounting between host and docker container + # If there are no files in the working directory, most likely volume mapping is misconfigured. + $containerId = GetContainerId + Write-Host "Validating volume mapping in the container $containerId" + $shellCommand = "docker exec -i $containerId /bin/bash -c 'ls $DockerWorkingDirectory'" + if (!$(Invoke-Expression $shellCommand)) { + Write-Error "Unable to validate volume mapping. For troubleshooting, follow instructions from http://aka.ms/DockerToolsTroubleshooting" + } +} + +# Runs docker run +function Run () { + $composeFilePath = GetComposeFilePath($pubPath) + + $shellCommand = "docker-compose -f '$composeFilePath' -p $ProjectName up -d" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression "cmd /c $shellCommand `"2>&1`"" + if ($LastExitCode -ne 0) { + Write-Error "Failed to start the container(s)" + } +} + +# Runs docker run +function Exec () { + $containerId = GetContainerId + $shellCommand = "docker exec -i $containerId $Command" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression $shellCommand + if ($LastExitCode -ne 0) { + Write-Error "Failed to exec command $Command in the container" + } +} + +function Refresh () { + # Find the container + $containerId = GetContainerId + + # Kill any existing process + $shellCommand = "docker exec -i $containerId /bin/bash -c 'if PID=`$(pidof -x $Command); then kill `$PID; fi'" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression $shellCommand + + # Publish the project + PublishProject + + # Restart the process + $shellCommand = "docker exec -i $containerId $Command" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression $shellCommand + if ($LastExitCode -ne 0) { + Write-Error "Failed to exec command $Command in the container" + } +} + +# Publishes the project +function PublishProject () { + $oldPath = $Env:Path + + try { + # Need to add $ProjectFolder\node_modules\.bin and the External Tools folder from the Web Tools to Path before calling publish + $newPath = (Join-Path $ProjectFolder ".\node_modules\.bin") + ";$oldPath" + + # Find where VS is installed + $vsPath = $null + if (Test-Path HKLM:\Software\WOW6432Node\Microsoft\VisualStudio\14.0\) { + $vsPath = (Get-ItemProperty -Path HKLM:\Software\WOW6432Node\Microsoft\VisualStudio\14.0\ -Name ShellFolder).ShellFolder + } elseif (Test-Path HKLM:\Software\Microsoft\VisualStudio\14.0\) { + $vsPath = (Get-ItemProperty -Path HKLM:\Software\Microsoft\VisualStudio\14.0\ -Name ShellFolder).ShellFolder + } + + # Find where the Web Tools are installed + if ($vsPath -ne $null) { + $webExternalPath = $null + # Check for the Web Tools in VS + if (Test-Path (Join-Path $vsPath "Web")) { + $webExternalPath = Join-Path $vsPath (Join-Path "Web" "External") + # or the Web Exress edition + } elseif (Test-Path (Join-Path $vsPath "WebExpress")) { + $webExternalPath = Join-Path $vsPath (Join-Path "WebExpress" "External") + } + # If the Web Tools were found, add the externals from the Web Tools to Path + if ($webExternalPath -ne $null) { + $newPath = "$newPath;$webExternalPath;$webExternalPath\git" + } + } + + # Set Path to our new path + $Env:Path = $newPath + + # Publish the project + dotnet publish -f $Framework -r $RuntimeID -c $Environment -o $pubPath $ProjectFolder + if ($? -eq $False) { + Write-Error "Failed to publish the project" + } + } + finally { + # Restore path to its old value + $Env:Path = $oldPath + } +} + +function GetComposeFilePath([string]$folder) { + $composeFileName = "docker-compose.yml" + if ($Environment -ne "Release") { + $composeFileName = "docker-compose.$($Environment.ToLower()).yml" + } + $composeFilePath = Join-Path $folder $composeFileName + + if (Test-Path $composeFilePath) { + return $composeFilePath + } else { + Write-Error -Message "$Environment is not a valid parameter. File '$composeFilePath' does not exist." -Category InvalidArgument + } +} + +# Need the full path of the project for mapping +$ProjectFolder = Resolve-Path $ProjectFolder + +if (![System.String]::IsNullOrWhiteSpace($Machine)) { + $users = Split-Path $env:USERPROFILE -Parent + + # Set the environment variables for the docker machine to connect to + $shellCommand = "docker-machine env $Machine --shell powershell" + Write-Verbose "Executing: $shellCommand | Invoke-Expression" + Invoke-Expression $shellCommand | Invoke-Expression + if ($LastExitCode -ne 0) { + Write-Error "Failed to set docker environment variables" + } + + # Get the driver name of the docker machine + $DriverName = (docker-machine inspect $Machine | Out-String | ConvertFrom-Json)."DriverName" + + # If the driver is virtualbox, need to check that the project location can be volume mapped + if ($DriverName -eq "virtualbox") { + if (!$ProjectFolder.StartsWith($users, [StringComparison]::InvariantCultureIgnoreCase)) { + $message = "VirtualBox by default shares C:\Users as c/Users. If the project is not under c:\Users, please manually add it to the shared folders on VirtualBox. "` + + "Follow instructions from https://www.virtualbox.org/manual/ch04.html#sharedfolders" + Write-Warning -Message $message + } + elseif (!$ProjectFolder.StartsWith($users, [StringComparison]::InvariantCulture)) { + # If the project is under C:\Users, fix the casing if necessary. Path in Linux is case sensitive and the default shared folder c/Users + # on VirtualBox can only be accessed if the project folder starts with the correct casing C:\Users as in $env:USERPROFILE + $ProjectFolder = $users + $ProjectFolder.Substring($users.Length) + } + } +} + +# Our working directory in bin +$dockerBinFolder = Join-Path $ProjectFolder (Join-Path "bin" "Docker") +# The folder to publish the app to +$pubPath = Join-Path (Join-Path $dockerBinFolder $Environment) "app" + +Write-Verbose "Setting: `$env:CLRDBG_VERSION = `"$ClrDebugVersion`"" +$env:CLRDBG_VERSION = "$ClrDebugVersion" + +if ($RemoteDebugging) { + Write-Verbose "Setting: `$env:REMOTE_DEBUGGING = 1" + $env:REMOTE_DEBUGGING = 1 +} +else { + Write-Verbose "Setting: `$env:REMOTE_DEBUGGING = 0" + $env:REMOTE_DEBUGGING = 0 +} + +# Call the correct functions for the parameters that were used +if ($Clean) { + Clean +} +if ($Build) { + Build +} +if ($Run) { + Run +} +if ($Exec) { + Exec +} +if ($Refresh) { + Refresh +} +if ($ValidateVolumeMapping) { + ValidateVolumeMapping +} diff --git a/src/Console/eShopConsole/Dockerfile b/src/Console/eShopConsole/Dockerfile new file mode 100644 index 000000000..22b88d135 --- /dev/null +++ b/src/Console/eShopConsole/Dockerfile @@ -0,0 +1,10 @@ +FROM microsoft/dotnet:1.0.0-core + +# Set the Working Directory +WORKDIR /app + +# Copy the app +COPY . /app + +# Start the app +ENTRYPOINT dotnet eShopConsole.dll diff --git a/src/Console/eShopConsole/Dockerfile.debug b/src/Console/eShopConsole/Dockerfile.debug new file mode 100644 index 000000000..88f551b3b --- /dev/null +++ b/src/Console/eShopConsole/Dockerfile.debug @@ -0,0 +1,20 @@ +FROM microsoft/dotnet:1.0.0-preview2-sdk + +ENV NUGET_XMLDOC_MODE skip + +# Install debugging components +ARG CLRDBG_VERSION=VS2015U2 +WORKDIR /clrdbg +RUN curl -SL https://raw.githubusercontent.com/Microsoft/MIEngine/getclrdbg-release/scripts/GetClrDbg.sh --output GetClrDbg.sh \ + && chmod 700 GetClrDbg.sh \ + && ./GetClrDbg.sh $CLRDBG_VERSION \ + && rm GetClrDbg.sh + +# Set the Working Directory +WORKDIR /app + +# Copy the app +COPY . /app + +# If we are launching through a remote debugger wait for it, otherwise start the app +ENTRYPOINT ["/bin/bash", "-c", "if [ \"$REMOTE_DEBUGGING\" -eq 0 ]; then dotnet eShopConsole.dll; else sleep infinity; fi"] diff --git a/src/Console/eShopConsole/Program.cs b/src/Console/eShopConsole/Program.cs new file mode 100644 index 000000000..612ade529 --- /dev/null +++ b/src/Console/eShopConsole/Program.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.Contracts; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.Repositories; +using Microsoft.EntityFrameworkCore; + +namespace eShopConsole +{ + public class Program + { + public static void Main(string[] args) + { + // All contexts that share the same service provider will share the same database + + //Using InMemory DB + //var options = DbContextUtil.CreateNewContextOptionsForInMemoryDB(); + + //Using Sql Server + var options = DbContextUtil.CreateNewContextOptionsForSqlDb(); + + // Run the test against one instance of the context + using (var context = new OrderingDbContext(options)) + { + IOrderRepository orderRepository = new OrderRepository(context); + + //Create generic Address ValueObject + Address sampleAddress = new Address("15703 NE 61st Ct.", + "Redmond", + "Washington", + "WA", + "United States", + "US", + "98052", + 47.661492, + -122.131309 + ); + //Create sample Orders + Order order1 = new Order(Guid.NewGuid(), sampleAddress, sampleAddress); + + //Add a few OrderItems + order1.AddNewOrderItem(Guid.NewGuid(), 2, 25, 30); + order1.AddNewOrderItem(Guid.NewGuid(), 1, 58, 0); + order1.AddNewOrderItem(Guid.NewGuid(), 1, 60, 0); + order1.AddNewOrderItem(Guid.NewGuid(), 3, 12, 0); + order1.AddNewOrderItem(Guid.NewGuid(), 5, 3, 0); + + orderRepository.Add(order1); + orderRepository.UnitOfWork.CommitAsync(); + + //With no Async Repository + //context.Orders.Add(order1); + //context.SaveChanges(); + + } + + //// Use a separate instance of the context to verify correct data was saved to database + using (var context = new OrderingDbContext(options)) + { + var orders = context.Orders + .Include(o => o.ShippingAddress) + .Include(o => o.BillingAddress) + .ToList(); + //Could be using .Load() if you don't want to create a List + + //OTHER SAMPLE + //var company = context.Companies + // .Include(co => co.Employees).ThenInclude(emp => emp.Employee_Car) + // .Include(co => co.Employees).ThenInclude(emp => emp.Employee_Country) + // .FirstOrDefault(co => co.companyID == companyID); + + //Assert when running test with a clean In-Memory DB + //Assert.Equal(1, context.Orders.Count()); + + string cityName = orders.First().ShippingAddress.City; + Console.WriteLine(cityName); + } + } + } +} diff --git a/src/Console/eShopConsole/Properties/AssemblyInfo.cs b/src/Console/eShopConsole/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..4c33ef24d --- /dev/null +++ b/src/Console/eShopConsole/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("eShopConsole")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c10c7b69-ce4f-4167-928e-33b7fa1dffc7")] diff --git a/src/Console/eShopConsole/Properties/Docker.props b/src/Console/eShopConsole/Properties/Docker.props new file mode 100644 index 000000000..4d1784ce8 --- /dev/null +++ b/src/Console/eShopConsole/Properties/Docker.props @@ -0,0 +1,17 @@ + + + + + + + + + + + dotnet + /app/eShopConsole.dll + /app/ + + diff --git a/src/Console/eShopConsole/Properties/Docker.targets b/src/Console/eShopConsole/Properties/Docker.targets new file mode 100644 index 000000000..da15fe7b6 --- /dev/null +++ b/src/Console/eShopConsole/Properties/Docker.targets @@ -0,0 +1,75 @@ + + + + False + + + + + + SetDockerProps; + CoreDockerBuild; + + + SetDockerProps; + CoreDockerClean; + + + SetDockerProps; + CoreDockerBeforeRebuild; + + + + + + + True + + + + + + + + + + + + + powershell -NonInteractive -ExecutionPolicy RemoteSigned .\DockerTask.ps1 -Build -Environment $(Configuration) -Machine '$(DockerMachineName)' -ClrDebugVersion VS2015U2 + $(DockerBuildCommand) -NoCache + + + + + + + + + + + + + + + + powershell -NonInteractive -ExecutionPolicy RemoteSigned .\DockerTask.ps1 -Clean -Environment $(Configuration) -Machine '$(DockerMachineName)' + + + + + + + + + + + + True + + + diff --git a/src/Console/eShopConsole/Properties/launchSettings.json b/src/Console/eShopConsole/Properties/launchSettings.json new file mode 100644 index 000000000..11c1111af --- /dev/null +++ b/src/Console/eShopConsole/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Docker": { + "executablePath": "%WINDIR%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "commandLineArgs": "-ExecutionPolicy RemoteSigned .\\DockerTask.ps1 -Run -Environment $(Configuration) -Machine '$(DockerMachineName)'" + } + } +} \ No newline at end of file diff --git a/src/Console/eShopConsole/docker-compose.debug.yml b/src/Console/eShopConsole/docker-compose.debug.yml new file mode 100644 index 000000000..75fcb8f73 --- /dev/null +++ b/src/Console/eShopConsole/docker-compose.debug.yml @@ -0,0 +1,12 @@ +version: '2' + +services: + eshopconsole: + image: username/eshopconsole:Debug + build: + context: . + dockerfile: Dockerfile.debug + environment: + - REMOTE_DEBUGGING=${REMOTE_DEBUGGING} + volumes: + - .:/app diff --git a/src/Console/eShopConsole/docker-compose.yml b/src/Console/eShopConsole/docker-compose.yml new file mode 100644 index 000000000..157b51d24 --- /dev/null +++ b/src/Console/eShopConsole/docker-compose.yml @@ -0,0 +1,8 @@ +version: '2' + +services: + eshopconsole: + image: username/eshopconsole + build: + context: . + dockerfile: Dockerfile diff --git a/src/Console/eShopConsole/eShopConsole.xproj b/src/Console/eShopConsole/eShopConsole.xproj new file mode 100644 index 000000000..dff9a112a --- /dev/null +++ b/src/Console/eShopConsole/eShopConsole.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + c10c7b69-ce4f-4167-928e-33b7fa1dffc7 + eShopConsole + .\obj + .\bin\ + v4.6 + + + 2.0 + 0.21 + + + + + \ No newline at end of file diff --git a/src/Console/eShopConsole/project.json b/src/Console/eShopConsole/project.json new file mode 100644 index 000000000..1b299277a --- /dev/null +++ b/src/Console/eShopConsole/project.json @@ -0,0 +1,31 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true, + "debugType": "portable" + }, + "dependencies": { + "Microsoft.EntityFrameworkCore": "1.0.0", + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + }, + "Ordering.API": "1.0.0-*", + "Ordering.Domain": "1.0.0-*", + "Ordering.SqlData": "1.0.0-*" + }, + "frameworks": { + "netcoreapp1.0": { + "imports": "dnxcore50" + } + }, + "publishOptions": { + "include": [ + "docker-compose.yml", + "docker-compose.debug.yml", + "Dockerfile.debug", + "Dockerfile", + ".dockerignore" + ] + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/.dockerignore b/src/Services/Ordering/Ordering.API/.dockerignore new file mode 100644 index 000000000..098ccd44a --- /dev/null +++ b/src/Services/Ordering/Ordering.API/.dockerignore @@ -0,0 +1,5 @@ +# Docker support files +docker-compose.yml +docker-compose.debug.yml +Dockerfile +Dockerfile.debug diff --git a/src/Services/Ordering/Ordering.API/Controllers/EnvironmentInfoController.cs b/src/Services/Ordering/Ordering.API/Controllers/EnvironmentInfoController.cs new file mode 100644 index 000000000..90e5f0ade --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Controllers/EnvironmentInfoController.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers +{ + [Route("api/[controller]")] + public class EnvironmentInfoController : Controller + { + // GET api/environmentInfo/machinename + [HttpGet("machinename")] + public dynamic GetMachineName() + { + return new + { + Node = Environment.MachineName + }; + } + + } +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Controllers/OrderingController.cs b/src/Services/Ordering/Ordering.API/Controllers/OrderingController.cs index c5064b812..0f5c7ffa5 100644 --- a/src/Services/Ordering/Ordering.API/Controllers/OrderingController.cs +++ b/src/Services/Ordering/Ordering.API/Controllers/OrderingController.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; using Microsoft.EntityFrameworkCore; -using Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.Contracts; using Microsoft.eShopOnContainers.Services.Ordering.SqlData.Queries; namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers @@ -16,15 +16,19 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers public class OrderingController : Controller { private IOrderRepository _orderRepository; - private OrderingQueries _queries; + private IOrderdingQueries _queries; + + //private OrderingDbContext _context; public OrderingController(IOrderRepository orderRepository, - OrderingQueries orderingQueries + IOrderdingQueries orderingQueries //, + //OrderingDbContext context ) { //Injected objects from the IoC container _orderRepository = orderRepository; _queries = orderingQueries; + //_context = context; } @@ -45,6 +49,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers return Ok(response); } + //(CDLTLL) - Using parameters //Alternate method if using several parameters instead of part of the URL // GET api/ordering/orders/?orderId=xxxGUIDxxx&otherParam=value //[HttpGet("orders")] @@ -55,34 +60,63 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers [HttpPut("orders/create")] public async Task Post([FromBody]Order order) { - return await _orderRepository.Add(order); - - //_context.Orders.Add(order); - //return await _context.SaveChangesAsync(); + _orderRepository.Add(order); + return await _orderRepository.UnitOfWork.CommitAsync(); } // PUT api/ordering/orders/xxxOrderGUIDxxxx/update [HttpPut("orders/{orderId:Guid}/update")] public async Task UpdateOrder(Guid orderID, [FromBody] Order orderToUpdate) { - return await _orderRepository.Add(orderToUpdate); - - //_context.Orders.Update(orderToUpdate); - //return await _context.SaveChangesAsync(); + _orderRepository.Update(orderToUpdate); + return await _orderRepository.UnitOfWork.CommitAsync(); } // DELETE api/ordering/orders/xxxOrderGUIDxxxx - [HttpDelete("orders/{orderId:Guid}/delete")] - public async Task Delete(Guid id) + [HttpDelete("orders/{orderId:Guid}/remove")] + public async Task Remove(Guid id) { - return await _orderRepository.Remove(id); + await _orderRepository.Remove(id); + return await _orderRepository.UnitOfWork.CommitAsync(); + } - //Order orderToDelete = _context.Orders - // .Where(o => o.Id == id) - // .SingleOrDefault(); - //_context.Orders.Remove(orderToDelete); - //return await _context.SaveChangesAsync(); + // GET api/ordering/orders/add_test_data_and_get_all + [HttpGet("orders/add_test_data_and_get_all")] + public async Task AddTestDataAndGetAllOrders() + { + //TEST ADDING ORDERS ********************************* + //Create generic Address ValueObject + Address sampleAddress = new Address("15703 NE 61st Ct.", + "Redmond", + "Washington", + "WA", + "United States", + "US", + "98052", + 47.661492, + -122.131309 + ); + //Create sample Orders + Order order1 = new Order(Guid.NewGuid(), sampleAddress, sampleAddress); + + //Add a few OrderItems + order1.AddNewOrderItem(Guid.NewGuid(), 2, 25, 30); + order1.AddNewOrderItem(Guid.NewGuid(), 1, 58, 0); + order1.AddNewOrderItem(Guid.NewGuid(), 1, 60, 0); + order1.AddNewOrderItem(Guid.NewGuid(), 3, 12, 0); + order1.AddNewOrderItem(Guid.NewGuid(), 5, 3, 0); + + _orderRepository.Add(order1); + int numRecs = await _orderRepository.UnitOfWork.CommitAsync(); + + //_context.Orders.Add(order1); + //_context.SaveChanges(); + + //***************************************************** + + dynamic response = await _queries.GetAllOrdersIncludingValueObjectsAndChildEntities(); + return Ok(response); } } diff --git a/src/Services/Ordering/Ordering.API/DockerTask.ps1 b/src/Services/Ordering/Ordering.API/DockerTask.ps1 new file mode 100644 index 000000000..eac1242be --- /dev/null +++ b/src/Services/Ordering/Ordering.API/DockerTask.ps1 @@ -0,0 +1,489 @@ +<# +.SYNOPSIS +Deploys an ASP .NET Core Web Application into a docker container running in a specified Docker machine. + +.DESCRIPTION +The following script will execute a set of Docker commands against the designated dockermachine. + +.PARAMETER Build +Builds the containers using docker-compose build. + +.PARAMETER Clean +Clears out any running containers (docker-compose kill, docker-compose rm -f). + +.PARAMETER Exec +Executes a command in the container using docker exec. + +.PARAMETER GetUrl +Gets the url for the site to open. + +.PARAMETER WaitForUrl +Waits for url to respond. + +.PARAMETER Refresh +Kills the running command in the container, publishes the project and restarts executing the command. + +.PARAMETER Run +Removes any conflicting containers running on the same port, then instances the containers using docker-compose up. + +.PARAMETER Environment +Specifies the configuration under which the project will be built and run (Debug or Release). + +.PARAMETER Machine +Specifies the docker machine name to connect to. This is optional and if left blank or not provided it will use the currently configured docker host, or if no host is set, will use the Docker for Windows beta. + +.PARAMETER ProjectFolder +Specifies the project folder, defaults to the parent of the directory containing this script. + +.PARAMETER ProjectName +Specifies the project name used by docker-compose, defaults to the name of $ProjectFolder. + +.PARAMETER NoCache +Specifies the build argument --no-cache. + +.PARAMETER OpenSite +Specifies whether to launch the site once the docker container is running, defaults to $True. + +.PARAMETER RemoteDebugging +Specifies if remote debugging is needed, defaults to $False. + +.PARAMETER ClrDebugVersion +Specifies the version of the debugger, defaults to 'VS2015U2'. + +.PARAMETER Command +Specifies the command to run in the container. + +.INPUTS +None. You cannot pipe inputs to DockerTask. + +.EXAMPLE +Compiles the project and builds the docker image using the currently configured docker host, or when no host is set, used for the Docker for Windows beta. To see the container running, use the -Run parameter +C:\PS> .\DockerTask.ps1 -Build -Environment Release + +.EXAMPLE +Will use 'docker-compose up' on the project, using the docker-machine instance named 'default', and opens a browser once the container responds. Assumes -Build was previously run. For the Docker for Windows Beta, remove the -Machine parameter or pass '' as the value. +C:\PS> .\DockerTask.ps1 -Run -Environment Release -Machine 'default' + +.LINK +http://aka.ms/DockerToolsForVS +#> + +Param( + [Parameter(ParameterSetName = "Build", Position = 0, Mandatory = $True)] + [switch]$Build, + [Parameter(ParameterSetName = "Clean", Position = 0, Mandatory = $True)] + [switch]$Clean, + [Parameter(ParameterSetName = "Run", Position = 0, Mandatory = $True)] + [switch]$Run, + [Parameter(ParameterSetName = "Exec", Position = 0, Mandatory = $True)] + [switch]$Exec, + [Parameter(ParameterSetName = "GetUrl", Position = 0, Mandatory = $True)] + [switch]$GetUrl, + [Parameter(ParameterSetName = "WaitForUrl", Position = 0, Mandatory = $True)] + [switch]$WaitForUrl, + [Parameter(ParameterSetName = "Refresh", Position = 0, Mandatory = $True)] + [switch]$Refresh, + [Parameter(ParameterSetName = "ValidateVolumeMapping", Position = 0, Mandatory = $True)] + [switch]$ValidateVolumeMapping, + [parameter(ParameterSetName = "Clean", Position = 1, Mandatory = $True)] + [parameter(ParameterSetName = "Build", Position = 1, Mandatory = $True)] + [parameter(ParameterSetName = "Run", Position = 1, Mandatory = $True)] + [parameter(ParameterSetName = "Refresh", Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String]$Environment, + [parameter(ParameterSetName = "Clean", Position = 2, Mandatory = $False)] + [parameter(ParameterSetName = "Build", Position = 2, Mandatory = $False)] + [parameter(ParameterSetName = "Run", Position = 2, Mandatory = $False)] + [parameter(ParameterSetName = "Exec", Position = 1, Mandatory = $False)] + [Parameter(ParameterSetName = "GetUrl", Position = 1, Mandatory = $False)] + [Parameter(ParameterSetName = "WaitForUrl", Position = 1, Mandatory = $False)] + [parameter(ParameterSetName = "Refresh", Position = 2, Mandatory = $False)] + [parameter(ParameterSetName = "ValidateVolumeMapping", Position = 1, Mandatory = $False)] + [String]$Machine, + [parameter(ParameterSetName = "Clean", Position = 3, Mandatory = $False)] + [parameter(ParameterSetName = "Build", Position = 3, Mandatory = $False)] + [parameter(ParameterSetName = "Run", Position = 3, Mandatory = $False)] + [parameter(ParameterSetName = "Exec", Position = 2, Mandatory = $False)] + [parameter(ParameterSetName = "Refresh", Position = 3, Mandatory = $False)] + [ValidateNotNullOrEmpty()] + [String]$ProjectFolder = (Split-Path -Path $MyInvocation.MyCommand.Definition), + [parameter(ParameterSetName = "Clean", Position = 4, Mandatory = $False)] + [parameter(ParameterSetName = "Build", Position = 4, Mandatory = $False)] + [parameter(ParameterSetName = "Run", Position = 4, Mandatory = $False)] + [parameter(ParameterSetName = "Exec", Position = 3, Mandatory = $False)] + [parameter(ParameterSetName = "Refresh", Position = 4, Mandatory = $False)] + [ValidateNotNullOrEmpty()] + [String]$ProjectName = (Split-Path -Path (Resolve-Path $ProjectFolder) -Leaf).ToLowerInvariant(), + [parameter(ParameterSetName = "Build", Position = 5, Mandatory = $False)] + [switch]$NoCache, + [parameter(ParameterSetName = "Run", Position = 5, Mandatory = $False)] + [bool]$OpenSite = $True, + [parameter(ParameterSetName = "Run", Position = 6, Mandatory = $False)] + [bool]$RemoteDebugging = $False, + [parameter(ParameterSetName = "Build", Position = 6, Mandatory = $False)] + [String]$ClrDebugVersion = "VS2015U2", + [parameter(ParameterSetName = "Exec", Position = 4, Mandatory = $True)] + [parameter(ParameterSetName = "Refresh", Position = 5, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String]$Command +) + +$ErrorActionPreference = "Stop" + +# Turns VERBOSE output ON +$VerbosePreference = "Continue" + +# Docker Working Directory for validating volume mapping. Should be in sync with Dockerfile and Docker.props. +$DockerWorkingDirectory = "/app/" + +# Path for the launch URL to be opened +$launchURLPath = "api/environmentInfo/machinename" + +# The project name can only contain alphanumeric charecters, replace everything else with empty string +$ProjectName = $ProjectName -replace "[^a-zA-Z0-9]", "" + +# The name of the image created by the compose file +$ImageName = "username/microsoft.eshoponcontainers.services.ordering.api" + +# Calculate the name of the container created by the compose file +$ContainerName = "${ProjectName}_microsoft.eshoponcontainers.services.ordering.api" + +# .net core runtime ID for the container (used to publish the app correctly) +$RuntimeID = "debian.8-x64" +# .net core framework (used to publish the app correctly) +$Framework = "netcoreapp1.0" + +# Kills all containers using an image, removes all containers using an image, and removes the image. +function Clean () { + $composeFilePath = GetComposeFilePath($ProjectFolder) + + # Call compose-down to clean up the containers + $shellCommand = "docker-compose -f '$composeFilePath' -p $ProjectName down" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression "cmd /c $shellCommand `"2>&1`"" + if ($LastExitCode -ne 0) { + Write-Error "Failed to clean up the containers" + } + + # If $ImageName exists remove it + $ImageNameRegEx = "\b$ImageName\b" + docker images | select-string -pattern $ImageNameRegEx | foreach { + $imageName = $_.Line.split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)[0]; + $tag = $_.Line.split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)[1]; + $shellCommand = "docker rmi -f ${imageName}:$tag" + Write-Verbose "Executing: $shellCommand"; + Invoke-Expression "cmd /c $shellCommand `"2>&1`"" + } + + # Remove any dangling images (from previous builds) + $shellCommand = "docker images -q --filter 'dangling=true'" + Write-Verbose "Executing: $shellCommand" + $danglingImages = $(Invoke-Expression "cmd /c $shellCommand `"2>&1`"") + if (-not [String]::IsNullOrWhiteSpace($danglingImages)) { + $shellCommand = "docker rmi -f $danglingImages" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression "cmd /c $shellCommand `"2>&1`"" + } + + # If the folder for publishing exists, delete it + if (Test-Path $pubPath) { + Remove-Item $pubPath -Force -Recurse + } +} + +# Runs docker build. +function Build () { + # Publish the project + PublishProject + + $composeFilePath = GetComposeFilePath($pubPath) + + $buildArgs = "" + if ($NoCache) + { + $buildArgs = "--no-cache" + } + + # Call docker-compose on the published project to build the images + $shellCommand = "docker-compose -f '$composeFilePath' -p $ProjectName build $buildArgs" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression "cmd /c $shellCommand `"2>&1`"" + if ($LastExitCode -ne 0) { + Write-Error "Failed to build the image" + } +} + +function GetContainerId () { + $containerId = (docker ps -f "name=$ContainerName" -q -n=1) + if ([System.String]::IsNullOrWhiteSpace($containerId)) { + Write-Error "Could not find a container named $ContainerName" + } + + $containerId +} + +# Validates volume mapping +function ValidateVolumeMapping () { + # Volume mapping enables shared folder mounting between host and docker container + # If there are no files in the working directory, most likely volume mapping is misconfigured. + $containerId = GetContainerId + Write-Host "Validating volume mapping in the container $containerId" + $shellCommand = "docker exec -i $containerId /bin/bash -c 'ls $DockerWorkingDirectory'" + if (!$(Invoke-Expression $shellCommand)) { + Write-Error "Unable to validate volume mapping. For troubleshooting, follow instructions from http://aka.ms/DockerToolsTroubleshooting" + } +} + +# Runs docker run +function Run () { + $composeFilePath = GetComposeFilePath($pubPath) + + $conflictingContainerIds = $(docker ps | select-string -pattern ":80->" | foreach { Write-Output $_.Line.split()[0] }) + + if ($conflictingContainerIds) { + $conflictingContainerIds = $conflictingContainerIds -Join ' ' + Write-Host "Stopping conflicting containers using port 80" + $stopCommand = "docker stop $conflictingContainerIds" + Write-Verbose "Executing: $stopCommand" + Invoke-Expression "cmd /c $stopCommand `"2>&1`"" + if ($LastExitCode -ne 0) { + Write-Error "Failed to stop the container(s)" + } + } + + $shellCommand = "docker-compose -f '$composeFilePath' -p $ProjectName up -d" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression "cmd /c $shellCommand `"2>&1`"" + if ($LastExitCode -ne 0) { + Write-Error "Failed to start the container(s)" + } + + if ($OpenSite) { + OpenSite + } +} + +# Runs docker run +function Exec () { + $containerId = GetContainerId + $shellCommand = "docker exec -i $containerId $Command" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression $shellCommand + if ($LastExitCode -ne 0) { + Write-Error "Failed to exec command $Command in the container" + } +} + +# Opens the remote site +function OpenSite () { + # If we're going to debug, the server won't start immediately; don't need to wait for it. + if (-not $RemoteDebugging) + { + $uri = GetUrl + + WaitForUrl $uri + + # Open the site. + Start-Process $uri + } + else { + # Give the container 10 seconds to get ready + Start-Sleep 10 + } +} + +# Gets the Url of the remote container +function GetUrl () { + if ([System.String]::IsNullOrWhiteSpace($Machine)) { + $launchURL = [System.UriBuilder]"http://localhost" + } + else { + $launchURL = [System.UriBuilder]"http://$(docker-machine ip $Machine)" + } + $launchURL.Path = $launchURLPath + return $launchURL.Uri.AbsoluteUri +} + +# Checks if the URL is responding +function WaitForUrl ([string]$uri) { + Write-Host "Opening site $uri " -NoNewline + $status = 0 + $count = 0 + + #Check if the site is available + while ($status -ne 200 -and $count -lt 120) { + try { + Write-Host "Trying to connect to $uri ($count/120)" + $response = Invoke-WebRequest -Uri $uri -Headers @{"Cache-Control"="no-cache";"Pragma"="no-cache"} -UseBasicParsing -Verbose:$false + $status = [int]$response.StatusCode + } + catch [System.Net.WebException] { } + if($status -ne 200) { + # Wait Time max. 2 minutes (120 sec.) + Start-Sleep 1 + $count += 1 + } + } + Write-Host + if($status -ne 200) { + # Check if bad volume mapping is the reason why we were not able to connect + ValidateVolumeMapping + } +} + +function Refresh () { + # Find the container + $containerId = GetContainerId + + # Kill any existing process + $shellCommand = "docker exec -i $containerId /bin/bash -c 'if PID=`$(pidof -x $Command); then kill `$PID; fi'" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression $shellCommand + + # Publish the project + PublishProject + + # Restart the process + $shellCommand = "docker exec -i $containerId $Command" + Write-Verbose "Executing: $shellCommand" + Invoke-Expression $shellCommand + if ($LastExitCode -ne 0) { + Write-Error "Failed to exec command $Command in the container" + } +} + +# Publishes the project +function PublishProject () { + $oldPath = $Env:Path + + try { + # Need to add $ProjectFolder\node_modules\.bin and the External Tools folder from the Web Tools to Path before calling publish + $newPath = (Join-Path $ProjectFolder ".\node_modules\.bin") + ";$oldPath" + + # Find where VS is installed + $vsPath = $null + if (Test-Path HKLM:\Software\WOW6432Node\Microsoft\VisualStudio\14.0\) { + $vsPath = (Get-ItemProperty -Path HKLM:\Software\WOW6432Node\Microsoft\VisualStudio\14.0\ -Name ShellFolder).ShellFolder + } elseif (Test-Path HKLM:\Software\Microsoft\VisualStudio\14.0\) { + $vsPath = (Get-ItemProperty -Path HKLM:\Software\Microsoft\VisualStudio\14.0\ -Name ShellFolder).ShellFolder + } + + # Find where the Web Tools are installed + if ($vsPath -ne $null) { + $webExternalPath = $null + # Check for the Web Tools in VS + if (Test-Path (Join-Path $vsPath "Web")) { + $webExternalPath = Join-Path $vsPath (Join-Path "Web" "External") + # or the Web Exress edition + } elseif (Test-Path (Join-Path $vsPath "WebExpress")) { + $webExternalPath = Join-Path $vsPath (Join-Path "WebExpress" "External") + } + # If the Web Tools were found, add the externals from the Web Tools to Path + if ($webExternalPath -ne $null) { + $newPath = "$newPath;$webExternalPath;$webExternalPath\git" + } + } + + # Set Path to our new path + $Env:Path = $newPath + + # Publish the project + dotnet publish -f $Framework -r $RuntimeID -c $Environment -o $pubPath $ProjectFolder + if ($? -eq $False) { + Write-Error "Failed to publish the project" + } + } + finally { + # Restore path to its old value + $Env:Path = $oldPath + } +} + +function GetComposeFilePath([string]$folder) { + $composeFileName = "docker-compose.yml" + if ($Environment -ne "Release") { + $composeFileName = "docker-compose.$($Environment.ToLower()).yml" + } + $composeFilePath = Join-Path $folder $composeFileName + + if (Test-Path $composeFilePath) { + return $composeFilePath + } else { + Write-Error -Message "$Environment is not a valid parameter. File '$composeFilePath' does not exist." -Category InvalidArgument + } +} + +# Need the full path of the project for mapping +$ProjectFolder = Resolve-Path $ProjectFolder + +if (![System.String]::IsNullOrWhiteSpace($Machine)) { + $users = Split-Path $env:USERPROFILE -Parent + + # Set the environment variables for the docker machine to connect to + $shellCommand = "docker-machine env $Machine --shell powershell" + Write-Verbose "Executing: $shellCommand | Invoke-Expression" + Invoke-Expression $shellCommand | Invoke-Expression + if ($LastExitCode -ne 0) { + Write-Error "Failed to set docker environment variables" + } + + # Get the driver name of the docker machine + $DriverName = (docker-machine inspect $Machine | Out-String | ConvertFrom-Json)."DriverName" + + # If the driver is virtualbox, need to check that the project location can be volume mapped + if ($DriverName -eq "virtualbox") { + if (!$ProjectFolder.StartsWith($users, [StringComparison]::InvariantCultureIgnoreCase)) { + $message = "VirtualBox by default shares C:\Users as c/Users. If the project is not under c:\Users, please manually add it to the shared folders on VirtualBox. "` + + "Follow instructions from https://www.virtualbox.org/manual/ch04.html#sharedfolders" + Write-Warning -Message $message + } + elseif (!$ProjectFolder.StartsWith($users, [StringComparison]::InvariantCulture)) { + # If the project is under C:\Users, fix the casing if necessary. Path in Linux is case sensitive and the default shared folder c/Users + # on VirtualBox can only be accessed if the project folder starts with the correct casing C:\Users as in $env:USERPROFILE + $ProjectFolder = $users + $ProjectFolder.Substring($users.Length) + } + } +} + +# Our working directory in bin +$dockerBinFolder = Join-Path $ProjectFolder (Join-Path "bin" "Docker") +# The folder to publish the app to +$pubPath = Join-Path (Join-Path $dockerBinFolder $Environment) "app" + +Write-Verbose "Setting: `$env:CLRDBG_VERSION = `"$ClrDebugVersion`"" +$env:CLRDBG_VERSION = "$ClrDebugVersion" + +if ($RemoteDebugging) { + Write-Verbose "Setting: `$env:REMOTE_DEBUGGING = 1" + $env:REMOTE_DEBUGGING = 1 +} +else { + Write-Verbose "Setting: `$env:REMOTE_DEBUGGING = 0" + $env:REMOTE_DEBUGGING = 0 +} + +# Call the correct functions for the parameters that were used +if ($Clean) { + Clean +} +if ($Build) { + Build +} +if ($Run) { + Run +} +if ($Exec) { + Exec +} +if ($GetUrl) { + GetUrl +} +if ($WaitForUrl) { + WaitForUrl (GetUrl) +} +if ($Refresh) { + Refresh +} +if ($ValidateVolumeMapping) { + ValidateVolumeMapping +} diff --git a/src/Services/Ordering/Ordering.API/Dockerfile b/src/Services/Ordering/Ordering.API/Dockerfile index 427ee81fc..73fbf1c1d 100644 --- a/src/Services/Ordering/Ordering.API/Dockerfile +++ b/src/Services/Ordering/Ordering.API/Dockerfile @@ -6,27 +6,25 @@ # Set the Working Directory WORKDIR /app -# Configure the listening port to 88 -ENV ASPNETCORE_URLS http://*:88 +# Configure the listening port to 80 +ENV ASPNETCORE_URLS http://*:80 # Open port exposed by Docker -EXPOSE 88/tcp +EXPOSE 80/tcp # Copy the app COPY . /app ################# - # Restore NuGet packages -RUN ["dotnet", "restore"] +#RUN ["dotnet", "restore"] # Build the .NET Core app -RUN ["dotnet", "build"] +#RUN ["dotnet", "build"] # Entrypoint -ENTRYPOINT ["dotnet", "run"] - +#ENTRYPOINT ["dotnet", "run"] ################# # Entry point through the copied assembly -#ENTRYPOINT dotnet Ordering.API.dll \ No newline at end of file +ENTRYPOINT dotnet Ordering.API.dll \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Dockerfile.debug b/src/Services/Ordering/Ordering.API/Dockerfile.debug new file mode 100644 index 000000000..321ccbfe1 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Dockerfile.debug @@ -0,0 +1,24 @@ +FROM microsoft/dotnet:1.0.0-preview2-sdk + +ENV NUGET_XMLDOC_MODE skip + +# Install debugging components +ARG CLRDBG_VERSION=VS2015U2 +WORKDIR /clrdbg +RUN curl -SL https://raw.githubusercontent.com/Microsoft/MIEngine/getclrdbg-release/scripts/GetClrDbg.sh --output GetClrDbg.sh \ + && chmod 700 GetClrDbg.sh \ + && ./GetClrDbg.sh $CLRDBG_VERSION \ + && rm GetClrDbg.sh + +# Set the Working Directory +WORKDIR /app + +# Configure the listening port to 80 +ENV ASPNETCORE_URLS http://*:80 +EXPOSE 80 + +# Copy the app +COPY . /app + +# If we are launching through a remote debugger wait for it, otherwise start the app +ENTRYPOINT ["/bin/bash", "-c", "if [ \"$REMOTE_DEBUGGING\" -eq 0 ]; then dotnet Ordering.API.dll; else sleep infinity; fi"] diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.Designer.cs b/src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.Designer.cs deleted file mode 100644 index 7482543fa..000000000 --- a/src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.Designer.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; - -namespace Ordering.API.Migrations -{ - [DbContext(typeof(OrderingDbContext))] - [Migration("20160909202620_MyFirstMigration")] - partial class MyFirstMigration - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { - modelBuilder - .HasAnnotation("ProductVersion", "1.0.0-rtm-21431") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("BuyerId"); - - b.Property("OrderDate"); - - b.Property("Status"); - - b.HasKey("Id"); - - b.ToTable("Orders"); - }); - } - } -} diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.cs b/src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.cs deleted file mode 100644 index 62b9ff652..000000000 --- a/src/Services/Ordering/Ordering.API/Migrations/20160909202620_MyFirstMigration.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Ordering.API.Migrations -{ - public partial class MyFirstMigration : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(nullable: false), - BuyerId = table.Column(nullable: false), - OrderDate = table.Column(nullable: false), - Status = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Orders"); - } - } -} diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.Designer.cs b/src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.Designer.cs deleted file mode 100644 index e2be6d29d..000000000 --- a/src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.Designer.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; - -namespace Ordering.API.Migrations -{ - [DbContext(typeof(OrderingDbContext))] - [Migration("20160909223213_Migration2")] - partial class Migration2 - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { - modelBuilder - .HasAnnotation("ProductVersion", "1.0.0-rtm-21431") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Address", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("City"); - - b.Property("Country"); - - b.Property("CountryCode"); - - b.Property("Latitude"); - - b.Property("Longitude"); - - b.Property("State"); - - b.Property("StateCode"); - - b.Property("Street"); - - b.Property("ZipCode"); - - b.HasKey("Id"); - - b.ToTable("Address"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("BillingAddressId"); - - b.Property("BuyerId"); - - b.Property("OrderDate"); - - b.Property("ShippingAddressId"); - - b.Property("Status"); - - b.HasKey("Id"); - - b.HasIndex("BillingAddressId"); - - b.HasIndex("ShippingAddressId"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Order", b => - { - b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Address", "BillingAddress") - .WithMany() - .HasForeignKey("BillingAddressId"); - - b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Address", "ShippingAddress") - .WithMany() - .HasForeignKey("ShippingAddressId"); - }); - } - } -} diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.cs b/src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.cs deleted file mode 100644 index 42f17a5d6..000000000 --- a/src/Services/Ordering/Ordering.API/Migrations/20160909223213_Migration2.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Ordering.API.Migrations -{ - public partial class Migration2 : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Address", - columns: table => new - { - Id = table.Column(nullable: false), - City = table.Column(nullable: true), - Country = table.Column(nullable: true), - CountryCode = table.Column(nullable: true), - Latitude = table.Column(nullable: false), - Longitude = table.Column(nullable: false), - State = table.Column(nullable: true), - StateCode = table.Column(nullable: true), - Street = table.Column(nullable: true), - ZipCode = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Address", x => x.Id); - }); - - migrationBuilder.AddColumn( - name: "BillingAddressId", - table: "Orders", - nullable: true); - - migrationBuilder.AddColumn( - name: "ShippingAddressId", - table: "Orders", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_Orders_BillingAddressId", - table: "Orders", - column: "BillingAddressId"); - - migrationBuilder.CreateIndex( - name: "IX_Orders_ShippingAddressId", - table: "Orders", - column: "ShippingAddressId"); - - migrationBuilder.AddForeignKey( - name: "FK_Orders_Address_BillingAddressId", - table: "Orders", - column: "BillingAddressId", - principalTable: "Address", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - - migrationBuilder.AddForeignKey( - name: "FK_Orders_Address_ShippingAddressId", - table: "Orders", - column: "ShippingAddressId", - principalTable: "Address", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Orders_Address_BillingAddressId", - table: "Orders"); - - migrationBuilder.DropForeignKey( - name: "FK_Orders_Address_ShippingAddressId", - table: "Orders"); - - migrationBuilder.DropIndex( - name: "IX_Orders_BillingAddressId", - table: "Orders"); - - migrationBuilder.DropIndex( - name: "IX_Orders_ShippingAddressId", - table: "Orders"); - - migrationBuilder.DropColumn( - name: "BillingAddressId", - table: "Orders"); - - migrationBuilder.DropColumn( - name: "ShippingAddressId", - table: "Orders"); - - migrationBuilder.DropTable( - name: "Address"); - } - } -} diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.Designer.cs b/src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.Designer.cs deleted file mode 100644 index cfccdd048..000000000 --- a/src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.Designer.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; - -namespace Ordering.API.Migrations -{ - [DbContext(typeof(OrderingDbContext))] - [Migration("20160909233852_Migration3")] - partial class Migration3 - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { - modelBuilder - .HasAnnotation("ProductVersion", "1.0.0-rtm-21431") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Address", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("City"); - - b.Property("Country"); - - b.Property("CountryCode"); - - b.Property("Latitude"); - - b.Property("Longitude"); - - b.Property("State"); - - b.Property("StateCode"); - - b.Property("Street"); - - b.Property("ZipCode"); - - b.HasKey("Id"); - - b.ToTable("Address"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("BillingAddressId"); - - b.Property("BuyerId"); - - b.Property("OrderDate"); - - b.Property("ShippingAddressId"); - - b.Property("Status"); - - b.HasKey("Id"); - - b.HasIndex("BillingAddressId"); - - b.HasIndex("ShippingAddressId"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Order", b => - { - b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Address", "BillingAddress") - .WithMany() - .HasForeignKey("BillingAddressId"); - - b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Address", "ShippingAddress") - .WithMany() - .HasForeignKey("ShippingAddressId"); - }); - } - } -} diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.cs b/src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.cs deleted file mode 100644 index 3b3ae2b73..000000000 --- a/src/Services/Ordering/Ordering.API/Migrations/20160909233852_Migration3.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Ordering.API.Migrations -{ - public partial class Migration3 : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160913052800_Migration4.Designer.cs b/src/Services/Ordering/Ordering.API/Migrations/20160913052800_Migration4.Designer.cs deleted file mode 100644 index c0386ae73..000000000 --- a/src/Services/Ordering/Ordering.API/Migrations/20160913052800_Migration4.Designer.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; - -namespace Ordering.API.Migrations -{ - [DbContext(typeof(OrderingDbContext))] - [Migration("20160913052800_Migration4")] - partial class Migration4 - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { - modelBuilder - .HasAnnotation("ProductVersion", "1.0.0-rtm-21431") - .HasAnnotation("Relational:Sequence:shared.OrderSequences", "'OrderSequences', 'shared', '1001', '1', '', '', 'Int32', 'False'") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Address", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("City"); - - b.Property("Country"); - - b.Property("CountryCode"); - - b.Property("Latitude"); - - b.Property("Longitude"); - - b.Property("State"); - - b.Property("StateCode"); - - b.Property("Street"); - - b.Property("ZipCode"); - - b.HasKey("Id"); - - b.ToTable("Address"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("BillingAddressId"); - - b.Property("BuyerId"); - - b.Property("OrderDate"); - - b.Property("SequenceNumber") - .ValueGeneratedOnAdd() - .HasDefaultValueSql("NEXT VALUE FOR shared.OrderSequences"); - - b.Property("ShippingAddressId"); - - b.Property("Status"); - - b.HasKey("Id"); - - b.HasIndex("BillingAddressId"); - - b.HasIndex("ShippingAddressId"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Order", b => - { - b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Address", "BillingAddress") - .WithMany() - .HasForeignKey("BillingAddressId"); - - b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.Address", "ShippingAddress") - .WithMany() - .HasForeignKey("ShippingAddressId"); - }); - } - } -} diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160913052800_Migration4.cs b/src/Services/Ordering/Ordering.API/Migrations/20160913052800_Migration4.cs deleted file mode 100644 index e719fd4c2..000000000 --- a/src/Services/Ordering/Ordering.API/Migrations/20160913052800_Migration4.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Ordering.API.Migrations -{ - public partial class Migration4 : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.EnsureSchema( - name: "shared"); - - migrationBuilder.CreateSequence( - name: "OrderSequences", - schema: "shared", - startValue: 1001L); - - migrationBuilder.AddColumn( - name: "SequenceNumber", - table: "Orders", - nullable: false, - defaultValueSql: "NEXT VALUE FOR shared.OrderSequences"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropSequence( - name: "OrderSequences", - schema: "shared"); - - migrationBuilder.DropColumn( - name: "SequenceNumber", - table: "Orders"); - } - } -} diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160913061710_Migration5.cs b/src/Services/Ordering/Ordering.API/Migrations/20160913061710_Migration5.cs deleted file mode 100644 index 1f005d177..000000000 --- a/src/Services/Ordering/Ordering.API/Migrations/20160913061710_Migration5.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Ordering.API.Migrations -{ - public partial class Migration5 : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "OrderItem", - columns: table => new - { - Id = table.Column(nullable: false), - Discount = table.Column(nullable: false), - FulfillmentRemaining = table.Column(nullable: false), - OrderId = table.Column(nullable: false), - ProductId = table.Column(nullable: false), - Quantity = table.Column(nullable: false), - UnitPrice = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItem", x => x.Id); - table.ForeignKey( - name: "FK_OrderItem_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_OrderId", - table: "OrderItem", - column: "OrderId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "OrderItem"); - } - } -} diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160913061710_Migration5.Designer.cs b/src/Services/Ordering/Ordering.API/Migrations/20160913204939_Migration1.Designer.cs similarity index 98% rename from src/Services/Ordering/Ordering.API/Migrations/20160913061710_Migration5.Designer.cs rename to src/Services/Ordering/Ordering.API/Migrations/20160913204939_Migration1.Designer.cs index e6af61b39..68d9f6d9e 100644 --- a/src/Services/Ordering/Ordering.API/Migrations/20160913061710_Migration5.Designer.cs +++ b/src/Services/Ordering/Ordering.API/Migrations/20160913204939_Migration1.Designer.cs @@ -8,8 +8,8 @@ using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; namespace Ordering.API.Migrations { [DbContext(typeof(OrderingDbContext))] - [Migration("20160913061710_Migration5")] - partial class Migration5 + [Migration("20160913204939_Migration1")] + partial class Migration1 { protected override void BuildTargetModel(ModelBuilder modelBuilder) { diff --git a/src/Services/Ordering/Ordering.API/Migrations/20160913204939_Migration1.cs b/src/Services/Ordering/Ordering.API/Migrations/20160913204939_Migration1.cs new file mode 100644 index 000000000..1d9dcc444 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Migrations/20160913204939_Migration1.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ordering.API.Migrations +{ + public partial class Migration1 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "shared"); + + migrationBuilder.CreateSequence( + name: "OrderSequences", + schema: "shared", + startValue: 1001L); + + migrationBuilder.CreateTable( + name: "Address", + columns: table => new + { + Id = table.Column(nullable: false), + City = table.Column(nullable: true), + Country = table.Column(nullable: true), + CountryCode = table.Column(nullable: true), + Latitude = table.Column(nullable: false), + Longitude = table.Column(nullable: false), + State = table.Column(nullable: true), + StateCode = table.Column(nullable: true), + Street = table.Column(nullable: true), + ZipCode = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Address", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(nullable: false), + BillingAddressId = table.Column(nullable: true), + BuyerId = table.Column(nullable: false), + OrderDate = table.Column(nullable: false), + SequenceNumber = table.Column(nullable: false, defaultValueSql: "NEXT VALUE FOR shared.OrderSequences"), + ShippingAddressId = table.Column(nullable: true), + Status = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + table.ForeignKey( + name: "FK_Orders_Address_BillingAddressId", + column: x => x.BillingAddressId, + principalTable: "Address", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Orders_Address_ShippingAddressId", + column: x => x.ShippingAddressId, + principalTable: "Address", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "OrderItem", + columns: table => new + { + Id = table.Column(nullable: false), + Discount = table.Column(nullable: false), + FulfillmentRemaining = table.Column(nullable: false), + OrderId = table.Column(nullable: false), + ProductId = table.Column(nullable: false), + Quantity = table.Column(nullable: false), + UnitPrice = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItem", x => x.Id); + table.ForeignKey( + name: "FK_OrderItem_Orders_OrderId", + column: x => x.OrderId, + principalTable: "Orders", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Orders_BillingAddressId", + table: "Orders", + column: "BillingAddressId"); + + migrationBuilder.CreateIndex( + name: "IX_Orders_ShippingAddressId", + table: "Orders", + column: "ShippingAddressId"); + + migrationBuilder.CreateIndex( + name: "IX_OrderItem_OrderId", + table: "OrderItem", + column: "OrderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropSequence( + name: "OrderSequences", + schema: "shared"); + + migrationBuilder.DropTable( + name: "OrderItem"); + + migrationBuilder.DropTable( + name: "Orders"); + + migrationBuilder.DropTable( + name: "Address"); + } + } +} diff --git a/src/Services/Ordering/Ordering.API/Migrations/OrderingContextModelSnapshot.cs b/src/Services/Ordering/Ordering.API/Migrations/OrderingDbContextModelSnapshot.cs similarity index 98% rename from src/Services/Ordering/Ordering.API/Migrations/OrderingContextModelSnapshot.cs rename to src/Services/Ordering/Ordering.API/Migrations/OrderingDbContextModelSnapshot.cs index 3ad13c891..0a404dfe0 100644 --- a/src/Services/Ordering/Ordering.API/Migrations/OrderingContextModelSnapshot.cs +++ b/src/Services/Ordering/Ordering.API/Migrations/OrderingDbContextModelSnapshot.cs @@ -8,7 +8,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; namespace Ordering.API.Migrations { [DbContext(typeof(OrderingDbContext))] - partial class OrderingContextModelSnapshot : ModelSnapshot + partial class OrderingDbContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { diff --git a/src/Services/Ordering/Ordering.API/Ordering.API.xproj b/src/Services/Ordering/Ordering.API/Ordering.API.xproj index 4781f6351..269107072 100644 --- a/src/Services/Ordering/Ordering.API/Ordering.API.xproj +++ b/src/Services/Ordering/Ordering.API/Ordering.API.xproj @@ -14,6 +14,9 @@ 2.0 + 0.21 + + \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Properties/Docker.props b/src/Services/Ordering/Ordering.API/Properties/Docker.props new file mode 100644 index 000000000..0bbf2529b --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Properties/Docker.props @@ -0,0 +1,17 @@ + + + + + + + + + + + dotnet + /app/Ordering.API.dll + /app/ + + diff --git a/src/Services/Ordering/Ordering.API/Properties/Docker.targets b/src/Services/Ordering/Ordering.API/Properties/Docker.targets new file mode 100644 index 000000000..da15fe7b6 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/Properties/Docker.targets @@ -0,0 +1,75 @@ + + + + False + + + + + + SetDockerProps; + CoreDockerBuild; + + + SetDockerProps; + CoreDockerClean; + + + SetDockerProps; + CoreDockerBeforeRebuild; + + + + + + + True + + + + + + + + + + + + + powershell -NonInteractive -ExecutionPolicy RemoteSigned .\DockerTask.ps1 -Build -Environment $(Configuration) -Machine '$(DockerMachineName)' -ClrDebugVersion VS2015U2 + $(DockerBuildCommand) -NoCache + + + + + + + + + + + + + + + + powershell -NonInteractive -ExecutionPolicy RemoteSigned .\DockerTask.ps1 -Clean -Environment $(Configuration) -Machine '$(DockerMachineName)' + + + + + + + + + + + + True + + + diff --git a/src/Services/Ordering/Ordering.API/Properties/launchSettings.json b/src/Services/Ordering/Ordering.API/Properties/launchSettings.json index e45264b5a..351182d71 100644 --- a/src/Services/Ordering/Ordering.API/Properties/launchSettings.json +++ b/src/Services/Ordering/Ordering.API/Properties/launchSettings.json @@ -11,7 +11,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "api/ordering/orders", + "launchUrl": "api/environmentInfo/machinename", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -19,10 +19,14 @@ "Microsoft.eShopOnContainers.Services.Ordering.API": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "http://localhost:5000/api/values", + "launchUrl": "http://localhost:5000/api/environmentInfo/machinename", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + }, + "Docker": { + "executablePath": "%WINDIR%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "commandLineArgs": "-ExecutionPolicy RemoteSigned .\\DockerTask.ps1 -Run -Environment $(Configuration) -Machine '$(DockerMachineName)'" } } } \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index d65553ec0..cda11d529 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; using Microsoft.EntityFrameworkCore; -using Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.Contracts; using Microsoft.eShopOnContainers.Services.Ordering.SqlData.Repositories; using Microsoft.eShopOnContainers.Services.Ordering.SqlData.Queries; @@ -38,17 +38,27 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API services.AddMvc(); //Add EF Core Context (UnitOfWork) - var connection = @"Server=(localdb)\mssqllocaldb;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"; - services.AddDbContext(options => options.UseSqlServer(connection) - .UseSqlServer(connection, b => b.MigrationsAssembly("Ordering.API")) + //SQL LocalDB + // var connString = @"Server=(localdb)\mssqllocaldb;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"; + + //SQL SERVER on-premises + //(Integrated Security) + //var connString = @"Server=CESARDLBOOKVHD;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"; + + //(SQL Server Authentication) + var connString = @"Server=CESARDLBOOKVHD;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;"; + + //(TBD) connString = config.GetConnectionString("SqlStandardAuthentication.OrderingDb"); + + services.AddDbContext(options => options.UseSqlServer(connString) + .UseSqlServer(connString, b => b.MigrationsAssembly("Ordering.API")) //(CDLTLL) MigrationsAssembly will be Ordering.SqlData, but when supported //Standard Library 1.6 by "Microsoft.EntityFrameworkCore.Tools" //Version "1.0.0-preview2-final" just supports .NET Core ); services.AddTransient(); - services.AddTransient(); - + services.AddTransient(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -57,6 +67,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); + //if (env.IsDevelopment()) + app.UseDeveloperExceptionPage(); + app.UseMvc(); } } diff --git a/src/Services/Ordering/Ordering.API/docker-compose.debug.yml b/src/Services/Ordering/Ordering.API/docker-compose.debug.yml new file mode 100644 index 000000000..c35be90c1 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/docker-compose.debug.yml @@ -0,0 +1,14 @@ +version: '2' + +services: + microsoft.eshoponcontainers.services.ordering.api: + image: username/microsoft.eshoponcontainers.services.ordering.api:Debug + build: + context: . + dockerfile: Dockerfile.debug + environment: + - REMOTE_DEBUGGING=${REMOTE_DEBUGGING} + ports: + - "80:80" + volumes: + - .:/app diff --git a/src/Services/Ordering/Ordering.API/docker-compose.yml b/src/Services/Ordering/Ordering.API/docker-compose.yml new file mode 100644 index 000000000..c52ef09e9 --- /dev/null +++ b/src/Services/Ordering/Ordering.API/docker-compose.yml @@ -0,0 +1,10 @@ +version: '2' + +services: + microsoft.eshoponcontainers.services.ordering.api: + image: username/microsoft.eshoponcontainers.services.ordering.api + build: + context: . + dockerfile: Dockerfile + ports: + - "80:80" diff --git a/src/Services/Ordering/Ordering.API/project.json b/src/Services/Ordering/Ordering.API/project.json index 8e0ffa433..822a647aa 100644 --- a/src/Services/Ordering/Ordering.API/project.json +++ b/src/Services/Ordering/Ordering.API/project.json @@ -18,15 +18,14 @@ "Microsoft.EntityFrameworkCore": "1.0.0", "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.0.0", "Ordering.Domain": "1.0.0-*", + "Ordering.SqlData": "1.0.0-*", "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final", - "Ordering.SqlData": "1.0.0-*" + "Microsoft.AspNetCore.Diagnostics": "1.0.0" }, - "tools": { "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final", "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, - "frameworks": { "netcoreapp1.0": { "imports": [ @@ -35,29 +34,33 @@ ] } }, - "buildOptions": { "emitEntryPoint": true, - "preserveCompilationContext": true + "preserveCompilationContext": true, + "debugType": "portable" }, - "runtimeOptions": { "configProperties": { "System.GC.Server": true } }, - "publishOptions": { "include": [ "wwwroot", "Views", "Areas/**/Views", "appsettings.json", - "web.config" + "web.config", + "docker-compose.yml", + "docker-compose.debug.yml", + "Dockerfile.debug", + "Dockerfile", + ".dockerignore" ] }, - "scripts": { - "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] + "postpublish": [ + "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" + ] } -} +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/Buyer/Buyer.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Buyer/Buyer.cs index 9157369a5..b10b087c4 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/Buyer/Buyer.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Buyer/Buyer.cs @@ -3,7 +3,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel { - public class Buyer : AggregateRoot + public class Buyer : Entity, IAggregateRoot { public Buyer(Guid buyerId, string name, string lastName, string email, Address address, string phoneNumber) { diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Order.cs index 4744c3cfa..069afa981 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Order.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/Order/Order.cs @@ -7,7 +7,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel { - public class Order : AggregateRoot + public class Order : Entity, IAggregateRoot { public Order(Guid buyerId, Address shippingAddress, Address billingAddress) : this(buyerId, shippingAddress, billingAddress, DateTime.UtcNow) diff --git a/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs b/src/Services/Ordering/Ordering.Domain/Contracts/IBuyerRepository.cs similarity index 69% rename from src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs rename to src/Services/Ordering/Ordering.Domain/Contracts/IBuyerRepository.cs index 826cf0e0d..fb5848ea8 100644 --- a/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IBuyerRepository.cs +++ b/src/Services/Ordering/Ordering.Domain/Contracts/IBuyerRepository.cs @@ -6,9 +6,9 @@ using System.Threading.Tasks; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; -namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts +namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Contracts { - public interface IBuyerRepository : IRepository + public interface IBuyerRepository : IRepository { //TBD - To define Specific Actions Not In Base Repo } diff --git a/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs b/src/Services/Ordering/Ordering.Domain/Contracts/IOrderRepository.cs similarity index 50% rename from src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs rename to src/Services/Ordering/Ordering.Domain/Contracts/IOrderRepository.cs index 70e6fd377..7e30b3dc7 100644 --- a/src/Services/Ordering/Ordering.Domain/RepositoryContracts/IOrderRepository.cs +++ b/src/Services/Ordering/Ordering.Domain/Contracts/IOrderRepository.cs @@ -6,13 +6,15 @@ using System.Threading.Tasks; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; -namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts + +namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Contracts { - public interface IOrderRepository : IRepository + public interface IOrderRepository : IRepository { - //TBD - To define Specific Actions Not In Base Repo - - Task Remove(Guid id); + void Add(Order order); + void Update(Order order); + Task Remove(Guid id); + Task FindAsync(Guid id); } - } + diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/IAggregateRoot.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/IAggregateRoot.cs new file mode 100644 index 000000000..6814ae065 --- /dev/null +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/IAggregateRoot.cs @@ -0,0 +1,6 @@ +namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork +{ + + public interface IAggregateRoot { } + +} diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs index fb19b8da1..c47975235 100644 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/IRepository.cs @@ -1,29 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; - -namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork +namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork { - /// - /// Base interface to implement a "Repository Pattern", for - /// more information about this pattern see http://martinfowler.com/eaaCatalog/repository.html - /// or http://blogs.msdn.com/adonet/archive/2009/06/16/using-repository-and-unit-of-work-patterns-with-entity-framework-4-0.aspx - /// - /// - /// Indeed, DbSet is already a generic repository and for Data-Driven apps - /// you might not need custom Repositories. - /// But using this interface allows us to ensure PI (Persistence Ignorance) principle - /// from the Domain and Application code - /// - /// Type of entity for this repository - public interface IRepository : IDisposable - where TEntity : AggregateRoot //1:1 relationship between Repository and AggregateRoot + public interface IRepository { - Task Add(TEntity item); - Task Remove(TEntity item); - Task Update(TEntity item); - Task Get(Guid id); + IUnitOfWork UnitOfWork { get; } } } diff --git a/src/Services/Ordering/Ordering.Domain/SeedWork/AggregateRoot.cs b/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs similarity index 59% rename from src/Services/Ordering/Ordering.Domain/SeedWork/AggregateRoot.cs rename to src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs index 99304f88b..802273f1f 100644 --- a/src/Services/Ordering/Ordering.Domain/SeedWork/AggregateRoot.cs +++ b/src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs @@ -1,11 +1,10 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork { - public class AggregateRoot : Entity + public interface IUnitOfWork : IDisposable { + Task CommitAsync(); } } diff --git a/src/Services/Ordering/Ordering.SqlData/Queries/IOrderdingQueries.cs b/src/Services/Ordering/Ordering.SqlData/Queries/IOrderdingQueries.cs new file mode 100644 index 000000000..783cf7a47 --- /dev/null +++ b/src/Services/Ordering/Ordering.SqlData/Queries/IOrderdingQueries.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.Queries +{ + //The OrderingQueries Contracts/Interfaces could be moved to a third assembly + //We're not putting this contract in the Domain layer assembly because + //queries/joins are just Application's needs and should not be limited + //to the Domain Model restrictions (Aggregates and Repositories restrictions). + // + //In this case we're using the same EF Context but another good approach + //is also to simply use SQL sentences for the queries with any Micro-ORM (like Dapper) or even just ADO.NET + // + //The point is that Queries are IDEMPOTENT and don't need to commit to DDD Domain restrictions + //so could be implemented in a completely orthogonal way in regards the Domain Layer (à la CQRS) + + public interface IOrderdingQueries + { + Task GetAllOrdersIncludingValueObjectsAndChildEntities(); + Task GetOrderById(Guid orderId); + } +} diff --git a/src/Services/Ordering/Ordering.SqlData/Queries/OrderingQueries.cs b/src/Services/Ordering/Ordering.SqlData/Queries/OrderingQueries.cs index 5504f4b20..c097acd82 100644 --- a/src/Services/Ordering/Ordering.SqlData/Queries/OrderingQueries.cs +++ b/src/Services/Ordering/Ordering.SqlData/Queries/OrderingQueries.cs @@ -7,10 +7,17 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; +using Microsoft.eShopOnContainers.Services.Ordering.SqlData.Queries; namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.Queries { - public class OrderingQueries + //In this case, for the Application queries, we're using the same EF Context but another good approach + //is also to simply use SQL sentences for the queries with any Micro-ORM (like Dapper) or even just ADO.NET + // + //The point is that Queries are IDEMPOTENT and don't need to commit to DDD Domain restrictions + //so could be implemented in a completely orthogonal way in regards the Domain Layer (à la CQRS) + + public class OrderingQueries : IOrderdingQueries { private OrderingDbContext _dbContext; diff --git a/src/Services/Ordering/Ordering.SqlData/Repositories/OrderRepository.cs b/src/Services/Ordering/Ordering.SqlData/Repositories/OrderRepository.cs index 4abe17b2f..7c8140d06 100644 --- a/src/Services/Ordering/Ordering.SqlData/Repositories/OrderRepository.cs +++ b/src/Services/Ordering/Ordering.SqlData/Repositories/OrderRepository.cs @@ -3,37 +3,48 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.eShopOnContainers.Services.Ordering.SqlData.SeedWork; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; -using Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.Contracts; using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; +using Microsoft.EntityFrameworkCore; namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.Repositories { //1:1 relationship between Repository and Aggregate (i.e. OrderRepository and Order) - public class OrderRepository - : Repository, IOrderRepository + public class OrderRepository : IOrderRepository { - public OrderRepository(OrderingDbContext unitOfWork) - : base(unitOfWork) { } + private readonly OrderingDbContext _context; - //TBD - To define Specific Actions Not In Base Repository class + public IUnitOfWork UnitOfWork => _context; - public async Task Remove(Guid orderId) + public OrderRepository(OrderingDbContext orderingDbContext) { - if (orderId == null) - return 0; - - Order orderToDelete = await this.Get(orderId); + _context = orderingDbContext; + } + public void Add(Order order) + { + _context.Orders.Add(order); + } - //attach item if not exist - _unitOfWork.Attach(orderToDelete); + public void Update(Order order) + { + _context.Orders.Update(order); + } - //set as "removed" - _unitOfWork.Remove(orderToDelete); + public async Task Remove(Guid orderId) + { + var orderToRemove = await _context.Orders.Where(o => o.Id == orderId).SingleOrDefaultAsync(); + _context.Orders.Remove(orderToRemove); + } - return await _unitOfWork.SaveChangesAsync(); + public async Task FindAsync(Guid id) + { + if (id != Guid.Empty) + return await _context.Set().FirstOrDefaultAsync(o => o.Id == id); + else + return null; } } diff --git a/src/Services/Ordering/Ordering.SqlData/SeedWork/Repository.cs b/src/Services/Ordering/Ordering.SqlData/SeedWork/Repository.cs deleted file mode 100644 index 539138930..000000000 --- a/src/Services/Ordering/Ordering.SqlData/SeedWork/Repository.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; - -namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.SeedWork -{ - public class Repository : IRepository - where TEntity : AggregateRoot //1:1 relationship between Repository and AggregateRoot - { - protected readonly DbContext _unitOfWork; - - //DbContext injected thru DI from ASP.NET Core bootstrap - public Repository(DbContext unitOfWork) - { - if (unitOfWork == null) - throw new ArgumentNullException("unitOfWork"); - - _unitOfWork = unitOfWork; - } - - public DbContext UnitOfWork - { - get - { - return _unitOfWork; - } - } - - public virtual async Task Add(TEntity item) - { - if (item == null) - return 0; - - _unitOfWork.Set().Add(item); // add new item in this set - - return await _unitOfWork.SaveChangesAsync(); - } - - public virtual async Task Remove(TEntity item) - { - if (item == null) - return 0; - - //attach item if not exist - _unitOfWork.Set().Attach(item); - - //set as "removed" - _unitOfWork.Set().Remove(item); - - return await _unitOfWork.SaveChangesAsync(); - } - - public virtual async Task Update(TEntity item) - { - if (item == null) - return 0; - - _unitOfWork.Set().Update(item); - - return await _unitOfWork.SaveChangesAsync(); - } - - public virtual async Task Get(Guid id) - { - if (id != Guid.Empty) - return await _unitOfWork.Set().FirstOrDefaultAsync(o => o.Id == id); - else - return null; - } - - public void Dispose() - { - if (_unitOfWork != null) - _unitOfWork.Dispose(); - } - - } -} diff --git a/src/Services/Ordering/Ordering.SqlData/UnitOfWork/DBContextUtil.cs b/src/Services/Ordering/Ordering.SqlData/UnitOfWork/DBContextUtil.cs index bb726c59b..4766c38d0 100644 --- a/src/Services/Ordering/Ordering.SqlData/UnitOfWork/DBContextUtil.cs +++ b/src/Services/Ordering/Ordering.SqlData/UnitOfWork/DBContextUtil.cs @@ -26,13 +26,23 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork return builder.Options; } - public static DbContextOptions CreateNewContextOptionsForSqlDB() + public static DbContextOptions CreateNewContextOptionsForSqlDb() { // Create a new options instance telling the context to use a Sql database var builder = new DbContextOptionsBuilder(); + //SQL LocalDB + //var connString = @"Server=(localdb)\mssqllocaldb;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"; + + //SQL SERVER on-premises + + //(Integrated Security) var connString = @"Server=CESARDLBOOKVHD;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"; + + //(SQL Server Authentication) + var connString = @"Server=CESARDLBOOKVHD;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;"; + //SQL LOCALDB - builder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"); + builder.UseSqlServer(connString); return builder.Options; } diff --git a/src/Services/Ordering/Ordering.SqlData/UnitOfWork/OrderingDbContext.cs b/src/Services/Ordering/Ordering.SqlData/UnitOfWork/OrderingDbContext.cs index 63142ee91..25ce0430c 100644 --- a/src/Services/Ordering/Ordering.SqlData/UnitOfWork/OrderingDbContext.cs +++ b/src/Services/Ordering/Ordering.SqlData/UnitOfWork/OrderingDbContext.cs @@ -4,10 +4,11 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork; namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork { - public class OrderingDbContext : DbContext + public class OrderingDbContext : DbContext, IUnitOfWork { public OrderingDbContext(DbContextOptions options) : base(options) @@ -21,8 +22,19 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork //and injected through DI later on. The following config is used when running Tests or similar contexts if (!optionsBuilder.IsConfigured) { + //SQL LocalDB + //var connString = @"Server=(localdb)\mssqllocaldb;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"; + + //SQL SERVER on-premises + + //(Integrated Security) + //var connString = @"Server=CESARDLBOOKVHD;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"; + + //(SQL Server Authentication) + var connString = @"Server=CESARDLBOOKVHD;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;"; + //SQL LOCALDB - optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Microsoft.eShopOnContainers.Services.OrderingDb;Trusted_Connection=True;"); + optionsBuilder.UseSqlServer(connString); } @@ -43,5 +55,26 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork .HasDefaultValueSql("NEXT VALUE FOR shared.OrderSequences"); } + + public async Task CommitAsync() + { + int changes = 0; + + try + { + //(CDLTLL) TBD + //RemoveOrphanedChilds(); + + changes = await base.SaveChangesAsync(); + } + catch (Exception ex) + { + //(CDLTLL) TBD + //RejectChanges(); + throw ex; + } + + return changes; + } } } diff --git a/test/Services/Ordering.Test/DataIntegrationTests.cs b/test/Services/Ordering.Test/DataIntegrationTests.cs index 160a7ed7b..ff0c93f65 100644 --- a/test/Services/Ordering.Test/DataIntegrationTests.cs +++ b/test/Services/Ordering.Test/DataIntegrationTests.cs @@ -1,15 +1,11 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; - using Xunit; using Microsoft.eShopOnContainers.Services.Ordering.SqlData.UnitOfWork; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel; -using Microsoft.eShopOnContainers.Services.Ordering.Domain.RepositoryContracts; +using Microsoft.eShopOnContainers.Services.Ordering.Domain.Contracts; using Microsoft.eShopOnContainers.Services.Ordering.SqlData.Repositories; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; namespace DataIntegrationTests { @@ -18,15 +14,15 @@ namespace DataIntegrationTests public class Tests { [Fact] - public void Add_order_to_data_model() + public async void Add_order_to_data_model() { // All contexts that share the same service provider will share the same database //Using InMemory DB //var options = DbContextUtil.CreateNewContextOptionsForInMemoryDB(); - //Using Sql LocalDB - var options = DbContextUtil.CreateNewContextOptionsForSqlDB(); + //Using Sql Server + var options = DbContextUtil.CreateNewContextOptionsForSqlDb(); // Run the test against one instance of the context using (var context = new OrderingDbContext(options)) @@ -55,6 +51,7 @@ namespace DataIntegrationTests order1.AddNewOrderItem(Guid.NewGuid(), 5, 3, 0); orderRepository.Add(order1); + int numChanges = await orderRepository.UnitOfWork.CommitAsync(); //With no Async Repository //context.Orders.Add(order1); @@ -69,7 +66,7 @@ namespace DataIntegrationTests .Include(o => o.ShippingAddress) .Include(o => o.BillingAddress) .ToList(); - //Could be using .Load() if you don't want to create a List + //Could be using .Load() if you don't want to create a List //OTHER SAMPLE //var company = context.Companies @@ -80,7 +77,8 @@ namespace DataIntegrationTests //Assert when running test with a clean In-Memory DB //Assert.Equal(1, context.Orders.Count()); - Assert.Equal("Redmond", orders.First().ShippingAddress.City); + string cityName = orders.First().ShippingAddress.City; + Assert.Equal("Redmond", cityName); } }