├── .gitignore ├── LICENSE ├── README.md └── src ├── Chapter01 ├── HelloMicroservices │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── CurrentDateTimeModule.cs │ ├── Dockerfile │ ├── HelloMicroservices.xproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── project.json │ └── web.config └── ch01.sln ├── Chapter02 ├── ShoppingCart │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Dockerfile │ ├── EventFeed │ │ ├── Event.cs │ │ ├── EventStore.cs │ │ ├── EventsFeedModule.cs │ │ └── IEventStore.cs │ ├── IProductCatalogueClient.cs │ ├── ProductCatalogueClient.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── ShoppingCart.xproj │ ├── ShoppingCart │ │ ├── IShoppingCartStore.cs │ │ ├── ShoppingCart.cs │ │ ├── ShoppingCartModule.cs │ │ └── ShoppingCartStore.cs │ ├── Startup.cs │ ├── project.json │ └── web.config └── ch02.sln ├── Chapter04 ├── .idea.Ch4 │ └── riderModule.iml ├── ApiGatewayMock │ ├── .gitignore │ ├── ApiGatewayMock.xproj │ ├── LoyalProgramClient.cs │ ├── Program.cs │ └── project.json ├── Ch4.sln ├── LoyaltyProgram │ ├── .gitignore │ ├── Bootstrapper.cs │ ├── Dockerfile │ ├── LoyaltyProgram.xproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── UsersModule.cs │ ├── YamlSerializerDeserializer.cs │ ├── project.json │ └── web.config └── LoyaltyProgramEventConsumer │ ├── .gitignore │ ├── LoyaltyProgramEventConsumer.xproj │ ├── Program.cs │ └── project.json ├── Chapter05 ├── Ch5.sln ├── ProductCatalog │ ├── .gitignore │ ├── Dockerfile │ ├── ProductCatalog.xproj │ ├── ProductsModule.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── project.json │ └── web.config └── ShoppingCart │ ├── .gitignore │ ├── .vscode │ ├── launch.json │ └── tasks.json │ ├── Dockerfile │ ├── EventFeed │ ├── Event.cs │ ├── EventStore.cs │ ├── EventsFeedModule.cs │ └── IEventStore.cs │ ├── ICache.cs │ ├── IProductCatalogueClient.cs │ ├── ProductCatalogueClient.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── README.md │ ├── ShoppingCart.xproj │ ├── ShoppingCart │ ├── IShoppingCartStore.cs │ ├── ShoppingCart.cs │ ├── ShoppingCartModule.cs │ └── ShoppingCartStore.cs │ ├── Startup.cs │ ├── database-scripts │ └── create-shopping-cart-db.sql │ ├── project.json │ └── web.config ├── Chapter06 ├── ApiGatewayMock │ ├── .gitignore │ ├── ApiGatewayMock.xproj │ ├── LoyaltyProgramClient.cs │ ├── Program.cs │ └── project.json ├── Ch6.sln ├── LoyaltyProgram │ ├── .gitignore │ ├── Bootstrapper.cs │ ├── Dockerfile │ ├── LoyaltyProgram.xproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── UsersModule.cs │ ├── YamlSerializerDeserializer.cs │ ├── project.json │ └── web.config └── LoyaltyProgramEventConsumer │ ├── .gitignore │ ├── LoyaltyProgramEventConsumer.xproj │ ├── Program.cs │ └── project.json ├── Chapter07 ├── .idea.Ch7 │ └── riderModule.iml ├── Ch7.sln ├── LoyaltyProgram │ ├── .gitignore │ ├── Bootstrapper.cs │ ├── Dockerfile │ ├── EventFeed │ │ ├── Event.cs │ │ ├── EventStore.cs │ │ ├── EventsFeedModule.cs │ │ └── IEventStore.cs │ ├── LoyaltyProgram.xproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── UsersModule.cs │ ├── YamlSerializerDeserializer.cs │ ├── project.json │ └── web.config ├── LoyaltyProgramEventConsumer │ ├── .gitignore │ ├── LoyaltyProgramEventConsumer.xproj │ ├── Program.cs │ └── project.json ├── LoyaltyProgramIntegrationTest │ ├── .gitignore │ ├── FakeEndpoints.cs │ ├── LoyaltyProgramIntegrationTest.xproj │ ├── RegisterUserAndGetNotificationScenario.cs │ └── project.json └── LoyaltyProgramUnitTests │ ├── .gitignore │ ├── EventFeed_should.cs │ ├── LoyaltyProgramUnitTests.xproj │ ├── TestModule_should.cs │ ├── UserModule_should.cs │ └── project.json ├── Chapter09 └── ShoppingCart │ ├── .gitignore │ ├── .vscode │ ├── launch.json │ └── tasks.json │ ├── Application_Packages │ └── LibOwin.cs │ ├── Dockerfile │ ├── EventFeed │ ├── Event.cs │ ├── EventStore.cs │ ├── EventsFeedModule.cs │ └── IEventStore.cs │ ├── ICache.cs │ ├── IProductCatalogueClient.cs │ ├── LoggingMiddleware.cs │ ├── MonitoringMiddleware.cs │ ├── ProductCatalogueClient.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── README.md │ ├── ShoppingCart.xproj │ ├── ShoppingCart │ ├── IShoppingCartStore.cs │ ├── ShoppingCart.cs │ ├── ShoppingCartModule.cs │ └── ShoppingCartStore.cs │ ├── Startup.cs │ ├── database-scripts │ └── create-shopping-cart-db.sql │ ├── project.json │ └── web.config ├── Chapter11 ├── .idea.ch11 │ └── riderModule.iml ├── HelloMicroservicesPlatform │ ├── .gitignore │ ├── Dockerfile │ ├── HelloMicroservicesPlatform.xproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── project.json │ └── web.config ├── MicroserivceNET.Auth │ ├── .gitignore │ ├── Application_Packages │ │ └── LibOwin.cs │ ├── AuthorizationMiddleware.cs │ ├── BuildFuncExtensions.cs │ ├── MicroserivceNET.Auth.xproj │ └── project.json ├── MicroserviceNET.Logging │ ├── .gitignore │ ├── Application_Packages │ │ └── LibOwin.cs │ ├── BuildFuncExtensions.cs │ ├── LoggingMiddleware.cs │ ├── MicroserviceNET.Logging.xproj │ ├── MonitoringMiddleware.cs │ └── project.json ├── MicroserviceNET.Platform │ ├── .gitignore │ ├── Application_Packages │ │ └── LibOwin.cs │ ├── HttpClientFactory.cs │ ├── MicroserviceNET.Platform.xproj │ ├── MicroservicePlatformHelper.cs │ └── project.json └── ch11.sln ├── Chapter12 ├── .idea.C12 │ └── riderModule.iml ├── ApiGateway │ ├── .gitignore │ ├── Dockerfile │ ├── GatewayModule.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── productlist.sshtml │ ├── project.json │ └── web.config ├── Login │ ├── .gitignore │ ├── Configuration │ │ ├── Clients.cs │ │ ├── Scopes.cs │ │ └── Users.cs │ ├── Dockerfile │ ├── Login.xproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── project.json │ └── web.config ├── ProductCatalog │ ├── .gitignore │ ├── Dockerfile │ ├── ProductsModule.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── project.json │ └── web.config ├── ShoppingCart │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Dockerfile │ ├── EventFeed │ │ ├── Event.cs │ │ ├── EventStore.cs │ │ ├── EventsFeedModule.cs │ │ └── IEventStore.cs │ ├── ICache.cs │ ├── IProductCatalogueClient.cs │ ├── ProductCatalogueClient.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── ShoppingCart.xproj │ ├── ShoppingCart │ │ ├── IShoppingCartStore.cs │ │ ├── ShoppingCart.cs │ │ ├── ShoppingCartModule.cs │ │ └── ShoppingCartStore.cs │ ├── Startup.cs │ ├── database-scripts │ │ └── create-shopping-cart-db.sql │ ├── project.json │ └── web.config └── start-app.ps1 ├── chapter03 └── readme.md ├── chapter08 └── OwinMiddlewareTests │ ├── .gitignore │ ├── LibOwin.cs │ ├── SampleTest.cs │ └── project.json ├── chapter10 ├── .idea.ch10 │ └── riderModule.iml ├── ApiGatewayMock │ ├── .gitignore │ ├── ApiGatewayMock.xproj │ ├── LoyalProgramClient.cs │ ├── Program.cs │ └── project.json ├── Login │ ├── .gitignore │ ├── Configuration │ │ ├── Clients.cs │ │ ├── Scopes.cs │ │ └── Users.cs │ ├── Dockerfile │ ├── Login.xproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── project.json │ └── web.config ├── LoyaltyProgram │ ├── .gitignore │ ├── Application_Packages │ │ └── LibOwin.cs │ ├── Bootstrapper.cs │ ├── Dockerfile │ ├── LoyaltyProgram.xproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── README.md │ ├── Startup.cs │ ├── UsersModule.cs │ ├── YamlSerializerDeserializer.cs │ ├── project.json │ └── web.config ├── ch10.sln └── start-app.ps1 └── global.json /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Christian Horsdal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code samples from Microservices in .NET Core 1st edition 2 | This repository contains the code from the **first** edition of the book. If you are looking for the code for the **second** edition go to https://github.com/horsdal/microservices-in-dotnet-book-second-edition 3 | 4 | The code samples from my microservices book - https://manning.com/books/microservices-in-net-core 5 | 6 | [![](https://images.manning.com/255/340/resize/book/f/562623d-102a-47c4-b12f-4c09af31441e/Horsdal-Microservices-HI.png)](https://www.manning.com/books/microservices-in-net-core) 7 | 8 | ## About the book 9 | Microservices in .NET Core shows you how to build and deploy secure and operations-friendly microservices using Nancy. The book takes you through an introduction to the microservices architectural style. Next, you'll learn important practical aspects of developing microservices from simple core concepts to more sophisticated. Throughout the book, you'll see many code examples implementing it with lightweight .NET technologies—most prominently Nancy. By the end, you'll be able to quickly and easily build reliable and operations-friendly microservices using Nancy, OWIN and other open technologies. 10 | 11 | ## Branches 12 | From time to time I will update the code use newer versions of libraries as well as .NET Core. I will keep each such update in a branch. The brances in the repository are: 13 | 14 | * `master`: The code as it appears in the book 15 | * `2017-05`: Everything updated to use `csproj` files instead of `project.json` files, so it's compatible with Visual Studio 2017 and newer `dotnet` command line versions. Furhtermore everything is updated to .NET Core 1.1 and Nancy 2.0.0-clinteastwood. 16 | -------------------------------------------------------------------------------- /src/Chapter01/HelloMicroservices/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (console)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/HelloMicroservices.dll", 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "stopAtEntry": false 13 | }, 14 | { 15 | "name": ".NET Core Launch (web)", 16 | "type": "coreclr", 17 | "request": "launch", 18 | "preLaunchTask": "build", 19 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/HelloMicroservices.dll", 20 | "args": [], 21 | "cwd": "${workspaceRoot}", 22 | "stopAtEntry": false, 23 | "launchBrowser": { 24 | "enabled": true, 25 | "args": "${auto-detect-url}", 26 | "windows": { 27 | "command": "cmd.exe", 28 | "args": "/C start ${auto-detect-url}" 29 | }, 30 | "osx": { 31 | "command": "open" 32 | }, 33 | "linux": { 34 | "command": "xdg-open" 35 | } 36 | } 37 | }, 38 | { 39 | "name": ".NET Core Attach", 40 | "type": "coreclr", 41 | "request": "attach", 42 | "processName": "" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /src/Chapter01/HelloMicroservices/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [], 10 | "isBuildCommand": true, 11 | "problemMatcher": "$msCompile" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/Chapter01/HelloMicroservices/CurrentDateTimeModule.cs: -------------------------------------------------------------------------------- 1 | namespace HelloMicroservices 2 | { 3 | using System; 4 | using Nancy; 5 | 6 | public class CurrentDateTimeModule 7 | : NancyModule 8 | { 9 | public CurrentDateTimeModule() 10 | { 11 | Get("/", _ => DateTime.UtcNow); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Chapter01/HelloMicroservices/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/Chapter01/HelloMicroservices/HelloMicroservices.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 74316f9a-d6b2-451a-b709-b3dc753d9a8c 10 | HelloMicroservices 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter01/HelloMicroservices/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace HelloMicroservices 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .Build(); 20 | 21 | host.Run(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Chapter01/HelloMicroservices/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "HelloMicroservices": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter01/HelloMicroservices/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace HelloMicroservices 2 | { 3 | using Microsoft.AspNetCore.Builder; 4 | using Nancy.Owin; 5 | 6 | public class Startup 7 | { 8 | public void Configure(IApplicationBuilder app) 9 | { 10 | app.UseOwin().UseNancy(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Chapter01/HelloMicroservices/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Microsoft.AspNetCore.Owin": "1.0.0", 10 | "Nancy": "2.0.0-barneyrubble" 11 | }, 12 | 13 | "tools": { 14 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 15 | }, 16 | 17 | "frameworks": { 18 | "netcoreapp1.0": { 19 | "imports": [ 20 | "dotnet5.6", 21 | "portable-net45+win8" 22 | ] 23 | } 24 | }, 25 | 26 | "buildOptions": { 27 | "emitEntryPoint": true, 28 | "preserveCompilationContext": true 29 | }, 30 | 31 | "runtimeOptions": { 32 | "gcServer": true 33 | }, 34 | 35 | "publishOptions": { 36 | "include": [ 37 | "wwwroot", 38 | "web.config" 39 | ] 40 | }, 41 | 42 | "scripts": { 43 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 44 | }, 45 | 46 | "tooling": { 47 | "defaultNamespace": "HelloMicroservices" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Chapter01/HelloMicroservices/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter01/ch01.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "HelloMicroservices", "HelloMicroservices\HelloMicroservices.xproj", "{74316F9A-D6B2-451A-B709-B3DC753D9A8C}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {74316F9A-D6B2-451A-B709-B3DC753D9A8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {74316F9A-D6B2-451A-B709-B3DC753D9A8C}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {74316F9A-D6B2-451A-B709-B3DC753D9A8C}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {74316F9A-D6B2-451A-B709-B3DC753D9A8C}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (console)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "stopAtEntry": false 13 | }, 14 | { 15 | "name": ".NET Core Launch (web)", 16 | "type": "coreclr", 17 | "request": "launch", 18 | "preLaunchTask": "build", 19 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", 20 | "args": [], 21 | "cwd": "${workspaceRoot}", 22 | "stopAtEntry": false, 23 | "launchBrowser": { 24 | "enabled": true, 25 | "args": "${auto-detect-url}", 26 | "windows": { 27 | "command": "cmd.exe", 28 | "args": "/C start ${auto-detect-url}" 29 | }, 30 | "osx": { 31 | "command": "open" 32 | }, 33 | "linux": { 34 | "command": "xdg-open" 35 | } 36 | } 37 | }, 38 | { 39 | "name": ".NET Core Attach", 40 | "type": "coreclr", 41 | "request": "attach", 42 | "processName": "" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [], 10 | "isBuildCommand": true, 11 | "problemMatcher": "$msCompile" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/EventFeed/Event.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using System; 4 | 5 | public struct Event 6 | { 7 | public long SequenceNumber { get; } 8 | public DateTimeOffset OccuredAt { get; } 9 | public string Name { get; } 10 | public object Content { get; } 11 | 12 | public Event( 13 | long sequenceNumber, 14 | DateTimeOffset occuredAt, 15 | string name, 16 | object content) 17 | { 18 | this.SequenceNumber = sequenceNumber; 19 | this.OccuredAt = occuredAt; 20 | this.Name = name; 21 | this.Content = content; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/EventFeed/EventStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | using ShoppingCart; 8 | 9 | public class EventStore : IEventStore 10 | { 11 | private static long currentSequenceNumber = 0; 12 | private static readonly IList database = new List(); 13 | 14 | public IEnumerable GetEvents( 15 | long firstEventSequenceNumber, 16 | long lastEventSequenceNumber) 17 | => database 18 | .Where(e => 19 | e.SequenceNumber >= firstEventSequenceNumber && 20 | e.SequenceNumber <= lastEventSequenceNumber) 21 | .OrderBy(e => e.SequenceNumber); 22 | 23 | public void Raise(string eventName, object content) 24 | { 25 | var seqNumber = Interlocked.Increment(ref currentSequenceNumber); 26 | database.Add( 27 | new Event( 28 | seqNumber, 29 | DateTimeOffset.UtcNow, 30 | eventName, 31 | content)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/EventFeed/EventsFeedModule.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using Nancy; 4 | 5 | public class EventsFeedModule : NancyModule 6 | { 7 | public EventsFeedModule(IEventStore eventStore) : base("/events") 8 | { 9 | Get("/", _ => 10 | { 11 | long firstEventSequenceNumber, lastEventSequenceNumber; 12 | if (!long.TryParse(this.Request.Query.start.Value, 13 | out firstEventSequenceNumber)) 14 | firstEventSequenceNumber = 0; 15 | if (!long.TryParse(this.Request.Query.end.Value, 16 | out lastEventSequenceNumber)) 17 | lastEventSequenceNumber = long.MaxValue; 18 | 19 | return 20 | eventStore.GetEvents( 21 | firstEventSequenceNumber, 22 | lastEventSequenceNumber); 23 | }); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/EventFeed/IEventStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ShoppingCart.ShoppingCart; 3 | 4 | namespace ShoppingCart.EventFeed 5 | { 6 | public interface IEventStore 7 | { 8 | IEnumerable GetEvents(long firstEventSequenceNumber, long lastEventSequenceNumber); 9 | void Raise(string eventName, object content); 10 | } 11 | } -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/IProductCatalogueClient.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using ShoppingCart; 6 | 7 | public interface IProductCatalogueClient 8 | { 9 | Task> 10 | GetShoppingCartItems(int[] productCatalogueIds); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace ShoppingCart 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var host = new WebHostBuilder() 11 | .UseKestrel() 12 | .UseContentRoot(Directory.GetCurrentDirectory()) 13 | .UseIISIntegration() 14 | .UseStartup() 15 | .Build(); 16 | 17 | host.Run(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "ShoppingCart": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/ShoppingCart.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10e4f601-c16b-4936-a7b4-d32d799318d1 10 | ShoppingCart 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/ShoppingCart/IShoppingCartStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | public interface IShoppingCartStore 4 | { 5 | ShoppingCart Get(int userId); 6 | void Save(ShoppingCart shoppingCart); 7 | } 8 | } -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/ShoppingCart/ShoppingCartModule.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | using EventFeed; 4 | using Nancy; 5 | using Nancy.ModelBinding; 6 | 7 | public class ShoppingCartModule : NancyModule 8 | { 9 | public ShoppingCartModule( 10 | IShoppingCartStore shoppingCartStore, 11 | IProductCatalogueClient productCatalogue, 12 | IEventStore eventStore) 13 | : base("/shoppingcart") 14 | { 15 | Get("/{userid:int}", parameters => 16 | { 17 | var userId = (int) parameters.userid; 18 | return shoppingCartStore.Get(userId); 19 | }); 20 | 21 | Post("/{userid:int}/items", async (parameters, _) => 22 | { 23 | var productCatalogueIds = this.Bind(); 24 | var userId = (int) parameters.userid; 25 | 26 | var shoppingCart = shoppingCartStore.Get(userId); 27 | var shoppingCartItems = await productCatalogue.GetShoppingCartItems(productCatalogueIds).ConfigureAwait(false); 28 | shoppingCart.AddItems(shoppingCartItems, eventStore); 29 | shoppingCartStore.Save(shoppingCart); 30 | 31 | return shoppingCart; 32 | }); 33 | 34 | Delete("/{userid:int}/items", parameters => 35 | { 36 | var productCatalogueIds = this.Bind(); 37 | var userId = (int)parameters.userid; 38 | 39 | var shoppingCart = shoppingCartStore.Get(userId); 40 | shoppingCart.RemoveItems(productCatalogueIds, eventStore); 41 | shoppingCartStore.Save(shoppingCart); 42 | 43 | return shoppingCart; 44 | }); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/ShoppingCart/ShoppingCartStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | using System.Collections.Generic; 4 | 5 | public class ShoppingCartStore : IShoppingCartStore 6 | { 7 | private static readonly Dictionary database = new Dictionary(); 8 | 9 | public ShoppingCart Get(int userId) 10 | { 11 | if (!database.ContainsKey(userId)) 12 | database[userId] = new ShoppingCart(userId); 13 | return database[userId]; 14 | } 15 | 16 | public void Save(ShoppingCart shoppingCart) 17 | { 18 | // Nothing needed. Saving would be needed with a real DB 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart 2 | { 3 | using Microsoft.AspNetCore.Builder; 4 | using Nancy; 5 | using Nancy.Configuration; 6 | using Nancy.Owin; 7 | 8 | public class Startup 9 | { 10 | public void Configure(IApplicationBuilder app) 11 | { 12 | app.UseOwin().UseNancy(opt => opt.Bootstrapper = new TracingBootstrapper()); 13 | } 14 | } 15 | 16 | public class TracingBootstrapper : Nancy.DefaultNancyBootstrapper 17 | { 18 | public override void Configure(INancyEnvironment env) 19 | { 20 | env.Tracing(enabled: true, displayErrorTraces: true); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Microsoft.AspNetCore.Owin": "1.0.0", 10 | "System.Net.Http": "4.0.1", 11 | "Nancy": "2.0.0-barneyrubble", 12 | "Polly": "4.2.1" 13 | }, 14 | 15 | "tools": { 16 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 17 | }, 18 | 19 | "frameworks": { 20 | "netcoreapp1.0": { 21 | "imports": [ 22 | "dotnet5.6", 23 | "dnxcore50", 24 | "portable-net45+win8" 25 | ] 26 | } 27 | }, 28 | 29 | "buildOptions": { 30 | "emitEntryPoint": true, 31 | "preserveCompilationContext": true 32 | }, 33 | 34 | "runtimeOptions": { 35 | "gcServer": true 36 | }, 37 | 38 | "publishOptions": { 39 | "include": [ 40 | "wwwroot", 41 | "web.config" 42 | ] 43 | }, 44 | 45 | "scripts": { 46 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 47 | }, 48 | 49 | "tooling": { 50 | "defaultNamespace": "HelloMicroservices" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Chapter02/ShoppingCart/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter02/ch02.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ShoppingCart", "ShoppingCart\ShoppingCart.xproj", "{10E4F601-C16B-4936-A7B4-D32D799318D1}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {10E4F601-C16B-4936-A7B4-D32D799318D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {10E4F601-C16B-4936-A7B4-D32D799318D1}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {10E4F601-C16B-4936-A7B4-D32D799318D1}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {10E4F601-C16B-4936-A7B4-D32D799318D1}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Chapter04/.idea.Ch4/riderModule.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Chapter04/ApiGatewayMock/ApiGatewayMock.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | fe109a02-bf1b-46fc-87c8-2a68781ce8a4 10 | ApiGatewayMock 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter04/ApiGatewayMock/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "emitEntryPoint": true 5 | }, 6 | 7 | "dependencies": { 8 | "Microsoft.NETCore.App": { 9 | "type": "platform", 10 | "version": "1.0.0" 11 | }, 12 | "Newtonsoft.Json": "8.0.3", 13 | "Polly": "4.2.1", 14 | "System.Net.Http": "4.0.1" 15 | }, 16 | 17 | "frameworks": { 18 | "netcoreapp1.0": { 19 | "imports": "dnxcore50" 20 | } 21 | }, 22 | 23 | "tooling": { 24 | "defaultNamespace": "ApiGatewayMock" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgram/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram 2 | { 3 | using System; 4 | using Nancy; 5 | using Nancy.Bootstrapper; 6 | 7 | public class Bootstrapper : DefaultNancyBootstrapper 8 | { 9 | protected override Func InternalConfiguration 10 | => NancyInternalConfiguration.WithOverrides(builder => builder.StatusCodeHandlers.Clear()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgram/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgram/LoyaltyProgram.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 4bed3c45-e8c3-4345-a08f-249a39a256eb 10 | LoyaltyProgram 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgram/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace LoyaltyProgram 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .Build(); 20 | 21 | host.Run(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgram/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "LoyaltyProgram": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgram/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.DependencyInjection; 9 | 10 | namespace LoyaltyProgram 11 | { 12 | public class Startup 13 | { 14 | // This method gets called by the runtime. Use this method to add services to the container. 15 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 16 | public void ConfigureServices(IServiceCollection services) 17 | { 18 | } 19 | 20 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 21 | public void Configure(IApplicationBuilder app) 22 | { 23 | app.Run(async (context) => 24 | { 25 | await context.Response.WriteAsync("Hello World!"); 26 | }); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgram/UsersModule.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram 2 | { 3 | using System.Collections.Generic; 4 | using Nancy; 5 | using Nancy.ModelBinding; 6 | 7 | public class UsersModule : NancyModule 8 | { 9 | private static IDictionary registeredUsers = 10 | new Dictionary(); 11 | 12 | public UsersModule() : base("/users") 13 | { 14 | Get("/", _ => registeredUsers.Values); 15 | 16 | Get("/{userId:int}", parameters => 17 | { 18 | int userId = parameters.userId; 19 | if (registeredUsers.ContainsKey(userId)) 20 | return registeredUsers[userId]; 21 | else 22 | return HttpStatusCode.NotFound; 23 | }); 24 | 25 | Post("/", _ => 26 | { 27 | var newUser = this.Bind(); 28 | this.AddRegisteredUser(newUser); 29 | return this.CreatedResponse(newUser); 30 | }); 31 | 32 | Put("/{userId:int}", parameters => 33 | { 34 | int userId = parameters.userId; 35 | var updatedUser = this.Bind(); 36 | registeredUsers[userId] = updatedUser; 37 | return updatedUser; 38 | }); 39 | } 40 | 41 | private dynamic CreatedResponse(LoyaltyProgramUser newUser) 42 | { 43 | return 44 | this.Negotiate 45 | .WithStatusCode(HttpStatusCode.Created) 46 | .WithHeader("Location", this.Request.Url.SiteBase + "/users/" + newUser.Id) 47 | .WithModel(newUser); 48 | } 49 | 50 | private void AddRegisteredUser(LoyaltyProgramUser newUser) 51 | { 52 | var userId = registeredUsers.Count; 53 | newUser.Id = userId; 54 | registeredUsers[userId] = newUser; 55 | } 56 | } 57 | 58 | public class LoyaltyProgramUser 59 | { 60 | public int Id { get; set; } 61 | public string Name { get; set; } 62 | public int LoyaltyPoints { get; set; } 63 | public LoyaltyProgramSettings Settings { get; set; } 64 | } 65 | 66 | public class LoyaltyProgramSettings 67 | { 68 | public string[] Interests { get; set; } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgram/YamlSerializerDeserializer.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using Nancy; 7 | using Nancy.ModelBinding; 8 | using Nancy.Responses.Negotiation; 9 | using YamlDotNet.Serialization; 10 | 11 | public class YamlBodyDeserializer : IBodyDeserializer 12 | { 13 | public bool CanDeserialize(MediaRange mediaRange, BindingContext context) 14 | => mediaRange.Subtype.ToString().EndsWith("yaml"); 15 | 16 | public object Deserialize(MediaRange mediaRange, Stream bodyStream, BindingContext context) 17 | { 18 | var yamlDeserializer = new Deserializer(); 19 | var reader = new StreamReader(bodyStream); 20 | return yamlDeserializer.Deserialize(reader, context.DestinationType); 21 | } 22 | } 23 | 24 | public class YamlBodySerializer : IResponseProcessor 25 | { 26 | public IEnumerable> ExtensionMappings 27 | { 28 | get 29 | { 30 | yield return new Tuple("yaml", new MediaRange("application/yaml")); 31 | } 32 | } 33 | 34 | public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context) 35 | => 36 | requestedMediaRange.Subtype.ToString().EndsWith("yaml") 37 | ? new ProcessorMatch { ModelResult = MatchResult.DontCare, RequestedContentTypeResult = MatchResult.NonExactMatch} 38 | : ProcessorMatch.None; 39 | 40 | public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context) 41 | => 42 | new Response 43 | { 44 | Contents = stream => 45 | { 46 | var yamlSerializer = new Serializer(); 47 | var streamWriter = new StreamWriter(stream); 48 | yamlSerializer.Serialize(streamWriter, model); 49 | streamWriter.Flush(); 50 | }, 51 | ContentType = "application/yaml" 52 | }; 53 | } 54 | } -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgram/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Nancy": "2.0.0-barneyrubble", 10 | "YamlDotNet": "3.8.0-pre233" 11 | }, 12 | 13 | "tools": { 14 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 15 | }, 16 | 17 | "frameworks": { 18 | "netcoreapp1.0": { 19 | "imports": [ 20 | "dotnet5.6", 21 | "portable-net45+win8" 22 | ] 23 | } 24 | }, 25 | 26 | "buildOptions": { 27 | "emitEntryPoint": true, 28 | "preserveCompilationContext": true 29 | }, 30 | 31 | "runtimeOptions": { 32 | "gcServer": true 33 | }, 34 | 35 | "publishOptions": { 36 | "include": [ 37 | "wwwroot", 38 | "web.config" 39 | ] 40 | }, 41 | 42 | "scripts": { 43 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 44 | }, 45 | 46 | "tooling": { 47 | "defaultNamespace": "LoyaltyProgram" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgram/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgramEventConsumer/LoyaltyProgramEventConsumer.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 47349717-585a-43ee-96e8-7d5249cb431d 10 | LoyaltyProgramEventConsumer 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter04/LoyaltyProgramEventConsumer/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "emitEntryPoint": true 5 | }, 6 | 7 | "dependencies": { 8 | "Newtonsoft.Json": "8.0.3", 9 | "System.ServiceProcess.ServiceController": "4.1.0", 10 | "System.Net.Http": "4.1.0" 11 | }, 12 | 13 | "frameworks": { 14 | "net461": { } 15 | }, 16 | 17 | "tooling": { 18 | "defaultNamespace": "LoyaltyProgramEventConsumer" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Chapter05/Ch5.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ShoppingCart", "ShoppingCart\ShoppingCart.xproj", "{10E4F601-C16B-4936-A7B4-D32D799318D1}" 7 | EndProject 8 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ProductCatalog", "ProductCatalog\ProductCatalog.xproj", "{46381F82-A705-496C-A0DF-7406EB036A22}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {10E4F601-C16B-4936-A7B4-D32D799318D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {10E4F601-C16B-4936-A7B4-D32D799318D1}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {10E4F601-C16B-4936-A7B4-D32D799318D1}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {10E4F601-C16B-4936-A7B4-D32D799318D1}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {46381F82-A705-496C-A0DF-7406EB036A22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {46381F82-A705-496C-A0DF-7406EB036A22}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {46381F82-A705-496C-A0DF-7406EB036A22}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {46381F82-A705-496C-A0DF-7406EB036A22}.Release|Any CPU.Build.0 = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | EndGlobal 33 | -------------------------------------------------------------------------------- /src/Chapter05/ProductCatalog/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/Chapter05/ProductCatalog/ProductCatalog.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0.25123 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 46381f82-a705-496c-a0df-7406eb036a22 10 | ProductCatalog 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter05/ProductCatalog/ProductsModule.cs: -------------------------------------------------------------------------------- 1 | namespace ProductCatalog 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Nancy; 7 | 8 | public class ProductsModule : NancyModule 9 | { 10 | public ProductsModule(ProductStore productStore) : base("/products") 11 | { 12 | Get("", _ => 13 | { 14 | string productIdsString = this.Request.Query.productIds; 15 | var productIds = ParseProductIdsFromQueryString(productIdsString); 16 | var products = productStore.GetProductsByIds(productIds); 17 | 18 | return 19 | this 20 | .Negotiate 21 | .WithModel(products) 22 | .WithHeader("cache-control", "max-age:86400"); 23 | }); 24 | } 25 | 26 | private static IEnumerable ParseProductIdsFromQueryString(string productIdsString) 27 | { 28 | return productIdsString.Split(',').Select(s => s.Replace("[", "").Replace("]", "")).Select(int.Parse); 29 | } 30 | } 31 | 32 | public interface ProductStore 33 | { 34 | IEnumerable GetProductsByIds(IEnumerable productIds); 35 | } 36 | 37 | public class StaticProductStore : ProductStore 38 | { 39 | public IEnumerable GetProductsByIds(IEnumerable productIds) 40 | { 41 | return productIds.Select(id => new ProductCatalogProduct(id, "foo" + id, "bar", new Money())); 42 | } 43 | } 44 | 45 | public class ProductCatalogProduct 46 | { 47 | public ProductCatalogProduct(int productId, string productName, string description, Money price) 48 | { 49 | this.ProductId = productId.ToString(); 50 | this.ProductName = productName; 51 | this.ProductDescription = description; 52 | this.Price = price; 53 | } 54 | public string ProductId { get; private set; } 55 | public string ProductName { get; private set; } 56 | public string ProductDescription { get; private set; } 57 | public Money Price { get; private set; } 58 | } 59 | 60 | public class Money { } 61 | } -------------------------------------------------------------------------------- /src/Chapter05/ProductCatalog/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace ProductCatalog 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .Build(); 20 | 21 | host.Run(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Chapter05/ProductCatalog/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "ProductCatalog": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter05/ProductCatalog/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace ProductCatalog 2 | { 3 | using Microsoft.AspNetCore.Builder; 4 | using Nancy.Owin; 5 | 6 | public class Startup 7 | { 8 | public void Configure(IApplicationBuilder app) 9 | { 10 | app.UseOwin().UseNancy(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Chapter05/ProductCatalog/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Microsoft.AspnetCore.Owin": "1.0.0", 10 | "Nancy": "2.0.0-barneyrubble" 11 | }, 12 | 13 | "tools": { 14 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 15 | }, 16 | 17 | "frameworks": { 18 | "netcoreapp1.0": { 19 | "imports": [ 20 | "dotnet5.6", 21 | "portable-net45+win8" 22 | ] 23 | } 24 | }, 25 | 26 | "buildOptions": { 27 | "emitEntryPoint": true, 28 | "preserveCompilationContext": true 29 | }, 30 | 31 | "runtimeOptions": { 32 | "gcServer": true 33 | }, 34 | 35 | "publishOptions": { 36 | "include": [ 37 | "wwwroot", 38 | "web.config" 39 | ] 40 | }, 41 | 42 | "scripts": { 43 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 44 | }, 45 | 46 | "tooling": { 47 | "defaultNamespace": "ProductCatalog" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Chapter05/ProductCatalog/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (console)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "stopAtEntry": false 13 | }, 14 | { 15 | "name": ".NET Core Launch (web)", 16 | "type": "coreclr", 17 | "request": "launch", 18 | "preLaunchTask": "build", 19 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", 20 | "args": [], 21 | "cwd": "${workspaceRoot}", 22 | "stopAtEntry": false, 23 | "launchBrowser": { 24 | "enabled": true, 25 | "args": "${auto-detect-url}", 26 | "windows": { 27 | "command": "cmd.exe", 28 | "args": "/C start ${auto-detect-url}" 29 | }, 30 | "osx": { 31 | "command": "open" 32 | }, 33 | "linux": { 34 | "command": "xdg-open" 35 | } 36 | } 37 | }, 38 | { 39 | "name": ".NET Core Attach", 40 | "type": "coreclr", 41 | "request": "attach", 42 | "processName": "" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [], 10 | "isBuildCommand": true, 11 | "problemMatcher": "$msCompile" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/EventFeed/Event.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using System; 4 | 5 | public struct Event 6 | { 7 | public long SequenceNumber { get; } 8 | public DateTimeOffset OccurredAt { get; } 9 | public string Name { get; } 10 | public object Content { get; } 11 | 12 | public Event( 13 | long sequenceNumber, 14 | DateTimeOffset occurredAt, 15 | string name, 16 | object content) 17 | { 18 | this.SequenceNumber = sequenceNumber; 19 | this.OccurredAt = occurredAt; 20 | this.Name = name; 21 | this.Content = content; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/EventFeed/EventsFeedModule.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using Nancy; 4 | 5 | public class EventsFeedModule : NancyModule 6 | { 7 | public EventsFeedModule(IEventStore eventStore) : base("/events") 8 | { 9 | Get("/", _ => 10 | { 11 | long firstEventSequenceNumber, lastEventSequenceNumber; 12 | if (!long.TryParse(this.Request.Query.start.Value, 13 | out firstEventSequenceNumber)) 14 | firstEventSequenceNumber = 0; 15 | if (!long.TryParse(this.Request.Query.end.Value, 16 | out lastEventSequenceNumber)) 17 | lastEventSequenceNumber = long.MaxValue; 18 | 19 | return 20 | eventStore.GetEvents( 21 | firstEventSequenceNumber, 22 | lastEventSequenceNumber); 23 | }); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/EventFeed/IEventStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | public interface IEventStore 7 | { 8 | Task> GetEvents(long firstEventSequenceNumber, 9 | long lastEventSequenceNumber); 10 | Task Raise(string eventName, object content); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/ICache.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | 7 | public interface ICache 8 | { 9 | void Add(string key, object value, TimeSpan ttl); 10 | object Get(string productsResource); 11 | } 12 | 13 | public class Cache : ICache 14 | { 15 | private static IDictionary> cache = new ConcurrentDictionary>(); 16 | 17 | public void Add(string key, object value, TimeSpan ttl) 18 | { 19 | cache[key] = Tuple.Create(DateTimeOffset.UtcNow.Add(ttl), value); 20 | } 21 | 22 | public object Get(string productsResource) 23 | { 24 | Tuple value; 25 | if (cache.TryGetValue(productsResource, out value) && value.Item1 > DateTimeOffset.UtcNow) 26 | return value; 27 | cache.Remove(productsResource); 28 | return null; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/IProductCatalogueClient.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using ShoppingCart; 6 | 7 | public interface IProductCatalogueClient 8 | { 9 | Task> 10 | GetShoppingCartItems(int[] productCatalogueIds); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace ShoppingCart 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var host = new WebHostBuilder() 11 | .UseKestrel() 12 | .UseContentRoot(Directory.GetCurrentDirectory()) 13 | .UseIISIntegration() 14 | .UseStartup() 15 | .Build(); 16 | 17 | host.Run(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "ShoppingCart": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/ShoppingCart.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10e4f601-c16b-4936-a7b4-d32d799318d1 10 | ShoppingCart 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/ShoppingCart/IShoppingCartStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | using System.Threading.Tasks; 4 | 5 | public interface IShoppingCartStore 6 | { 7 | Task Get(int userId); 8 | Task Save(ShoppingCart shoppingCart); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/ShoppingCart/ShoppingCartModule.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | using EventFeed; 4 | using Nancy; 5 | using Nancy.ModelBinding; 6 | 7 | public class ShoppingCartModule : NancyModule 8 | { 9 | public ShoppingCartModule( 10 | IShoppingCartStore shoppingCartStore, 11 | IProductCatalogueClient productCatalogue, 12 | IEventStore eventStore) 13 | : base("/shoppingcart") 14 | { 15 | Get("/{userid:int}", parameters => 16 | { 17 | var userId = (int) parameters.userid; 18 | return shoppingCartStore.Get(userId); 19 | }); 20 | 21 | Post("/{userid:int}/items", async parameters => 22 | { 23 | var productCatalogueIds = this.Bind(); 24 | var userId = (int) parameters.userid; 25 | 26 | var shoppingCart = await shoppingCartStore.Get(userId).ConfigureAwait(false); 27 | var shoppingCartItems = await productCatalogue.GetShoppingCartItems(productCatalogueIds).ConfigureAwait(false); 28 | shoppingCart.AddItems(shoppingCartItems, eventStore); 29 | shoppingCartStore.Save(shoppingCart); 30 | 31 | return shoppingCart; 32 | }); 33 | 34 | Delete("/{userid:int}/items", async parameters => 35 | { 36 | var productCatalogueIds = this.Bind(); 37 | var userId = (int)parameters.userid; 38 | 39 | var shoppingCart = await shoppingCartStore.Get(userId).ConfigureAwait(false); 40 | shoppingCart.RemoveItems(productCatalogueIds, eventStore); 41 | shoppingCartStore.Save(shoppingCart); 42 | 43 | return shoppingCart; 44 | }); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/ShoppingCart/ShoppingCartStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data.SqlClient; 6 | using System.Threading.Tasks; 7 | using Dapper; 8 | 9 | public class ShoppingCartStore : IShoppingCartStore 10 | { 11 | private string connectionString = 12 | @"Data Source=.\SQLEXPRESS;Initial Catalog=ShoppingCart; 13 | Integrated Security=True"; 14 | 15 | private const string readItemsSql = 16 | @"select * from ShoppingCart, ShoppingCartItems 17 | where ShoppingCartItems.ShoppingCartId = ID 18 | and ShoppingCart.UserId=@UserId"; 19 | 20 | public async Task Get(int userId) 21 | { 22 | using (var conn = new SqlConnection(connectionString)) 23 | { 24 | var items = await 25 | conn.QueryAsync( 26 | readItemsSql, 27 | new { UserId = userId }); 28 | return new ShoppingCart(userId, items); 29 | } 30 | } 31 | 32 | private const string deleteAllForShoppingCartSql= 33 | @"delete item from ShoppingCartItems item 34 | inner join ShoppingCart cart on item.ShoppingCartId = cart.ID 35 | and cart.UserId=@UserId"; 36 | 37 | private const string addAllForShoppingCartSql= 38 | @"insert into ShoppingCartItems 39 | (ShoppingCartId, ProductCatalogId, ProductName, 40 | ProductDescription, Amount, Currency) 41 | values 42 | (@ShoppingCartId, @ProductCatalogId, @ProductName,v 43 | @ProductDescription, @Amount, @Currency)"; 44 | 45 | public async Task Save(ShoppingCart shoppingCart) 46 | { 47 | using (var conn = new SqlConnection(connectionString)) 48 | using (var tx = conn.BeginTransaction()) 49 | { 50 | await conn.ExecuteAsync( 51 | deleteAllForShoppingCartSql, 52 | new { UserId = shoppingCart.UserId }, 53 | tx).ConfigureAwait(false); 54 | await conn.ExecuteAsync( 55 | addAllForShoppingCartSql, 56 | shoppingCart.Items, 57 | tx).ConfigureAwait(false); 58 | } 59 | } 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart 2 | { 3 | using Microsoft.AspNetCore.Builder; 4 | using Nancy; 5 | using Nancy.Configuration; 6 | using Nancy.Owin; 7 | 8 | public class Startup 9 | { 10 | public void Configure(IApplicationBuilder app) 11 | { 12 | app.UseOwin().UseNancy(opt => opt.Bootstrapper = new TracingBootstrapper()); 13 | } 14 | } 15 | 16 | public class TracingBootstrapper : Nancy.DefaultNancyBootstrapper 17 | { 18 | public override void Configure(INancyEnvironment env) 19 | { 20 | env.Tracing(enabled: true, displayErrorTraces: true); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/database-scripts/create-shopping-cart-db.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE ShoppingCart 2 | GO 3 | 4 | USE [ShoppingCart] 5 | GO 6 | 7 | CREATE TABLE [dbo].[ShoppingCart]( 8 | [ID] int IDENTITY(1,1) PRIMARY KEY, 9 | [UserId] [bigint] NOT NULL, 10 | CONSTRAINT ShoppingCartUnique UNIQUE([ID], [UserID]) 11 | ) 12 | GO 13 | 14 | CREATE INDEX ShoppingCart_UserId 15 | ON [dbo].[ShoppingCart] (UserId) 16 | GO 17 | 18 | CREATE TABLE [dbo].[ShoppingCartItems]( 19 | [ID] int IDENTITY(1,1) PRIMARY KEY, 20 | [ShoppingCartId] [int] NOT NULL, 21 | [ProductCatalogId] [bigint] NOT NULL, 22 | [ProductName] [nvarchar](100) NOT NULL, 23 | [ProductDescription] [nvarchar](500) NULL, 24 | [Amount] [int] NOT NULL, 25 | [Currency] [nvarchar](5) NOT NULL 26 | ) 27 | 28 | GO 29 | 30 | ALTER TABLE [dbo].[ShoppingCartItems] WITH CHECK ADD CONSTRAINT [FK_ShoppingCart] FOREIGN KEY([ShoppingCartId]) 31 | REFERENCES [dbo].[ShoppingCart] ([Id]) 32 | GO 33 | 34 | ALTER TABLE [dbo].[ShoppingCartItems] CHECK CONSTRAINT [FK_ShoppingCart] 35 | GO 36 | 37 | CREATE INDEX ShoppingCartItems_ShoppingCartId 38 | ON [dbo].[ShoppingCartItems] (ShoppingCartId) 39 | GO 40 | 41 | CREATE TABLE [dbo].[EventStore]( 42 | [ID] int IDENTITY(1,1) PRIMARY KEY, 43 | [Name] [nvarchar](100) NOT NULL, 44 | [OccurredAt] [datetimeoffset] NOT NULL, 45 | [Content][nvarchar](max) NOT NULL 46 | ) 47 | GO 48 | 49 | -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 4 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 5 | "Microsoft.AspNetCore.Owin": "1.0.0", 6 | "Nancy": "2.0.0-barneyrubble", 7 | "Polly": "4.2.1", 8 | "Dapper": "1.50.0-rc2a" 9 | }, 10 | "runtimes": { 11 | "win10-x64": "" 12 | }, 13 | 14 | "tools": { 15 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 16 | }, 17 | 18 | "frameworks": { 19 | "netcoreapp1.0": { 20 | "imports": [ 21 | "dotnet5.6", 22 | "portable-net45+win8" 23 | ], 24 | "dependencies": { 25 | "System.Net.Http": "4.0.1" 26 | } 27 | }, 28 | "net461": { 29 | "compilationOptions": {"define": ["net461"]}, 30 | "frameworkAssemblies": { 31 | "System.Net.Http": "4.0.0.0", 32 | "System.Runtime": "4.0.20.0" 33 | }, 34 | "dependencies": { 35 | "EventStore.Client": "3.3.1", 36 | "Microsoft.CSharp": "4.0.0" 37 | } 38 | } 39 | }, 40 | 41 | "buildOptions": { 42 | "emitEntryPoint": true, 43 | "preserveCompilationContext": true 44 | }, 45 | 46 | "runtimeOptions": { 47 | "gcServer": true 48 | }, 49 | 50 | "publishOptions": { 51 | "include": [ 52 | "wwwroot", 53 | "web.config" 54 | ] 55 | }, 56 | 57 | "scripts": { 58 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 59 | }, 60 | 61 | "tooling": { 62 | "defaultNamespace": "HelloMicroservices" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Chapter05/ShoppingCart/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter06/ApiGatewayMock/ApiGatewayMock.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | fe109a02-bf1b-46fc-87c8-2a68781ce8a4 10 | ApiGatewayMock 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter06/ApiGatewayMock/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "emitEntryPoint": true 5 | }, 6 | 7 | "dependencies": { 8 | "Microsoft.NETCore.App": { 9 | "type": "platform", 10 | "version": "1.0.0" 11 | }, 12 | "Newtonsoft.Json": "8.0.3", 13 | "Polly": "4.2.1", 14 | "System.Net.Http": "4.0.1" 15 | }, 16 | 17 | "frameworks": { 18 | "netcoreapp1.0": { 19 | "imports": "dnxcore50" 20 | } 21 | }, 22 | 23 | "tooling": { 24 | "defaultNamespace": "ApiGatewayMock" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgram/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram 2 | { 3 | using System; 4 | using Nancy; 5 | using Nancy.Bootstrapper; 6 | using Nancy.TinyIoc; 7 | 8 | public class Bootstrapper : DefaultNancyBootstrapper 9 | { 10 | protected override Func InternalConfiguration 11 | => NancyInternalConfiguration.WithOverrides(builder => builder.StatusCodeHandlers.Clear()); 12 | 13 | protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) 14 | { 15 | pipelines.OnError += (ctx, ex) => 16 | { 17 | // write to central log store 18 | return null; 19 | }; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgram/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgram/LoyaltyProgram.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 4bed3c45-e8c3-4345-a08f-249a39a256eb 10 | LoyaltyProgram 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgram/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace LoyaltyProgram 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .Build(); 20 | 21 | host.Run(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgram/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "LoyaltyProgram": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgram/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.DependencyInjection; 9 | 10 | namespace LoyaltyProgram 11 | { 12 | public class Startup 13 | { 14 | // This method gets called by the runtime. Use this method to add services to the container. 15 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 16 | public void ConfigureServices(IServiceCollection services) 17 | { 18 | } 19 | 20 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 21 | public void Configure(IApplicationBuilder app) 22 | { 23 | app.Run(async (context) => 24 | { 25 | await context.Response.WriteAsync("Hello World!"); 26 | }); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgram/UsersModule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Nancy; 3 | using Nancy.ModelBinding; 4 | 5 | namespace LoyaltyProgram 6 | { 7 | public class UsersModule : NancyModule 8 | { 9 | private static IDictionary registerUsers = 10 | new Dictionary(); 11 | 12 | public UsersModule() : base("/users") 13 | { 14 | Get("/", _ => registerUsers.Values); 15 | 16 | Get("/{userId:int}", parameters => 17 | { 18 | int userId = parameters.userId; 19 | if (registerUsers.ContainsKey(userId)) 20 | return registerUsers[userId]; 21 | else 22 | return HttpStatusCode.NotFound; 23 | }); 24 | 25 | Post("/", _ => 26 | { 27 | var newUser = this.Bind(); 28 | this.AddRegisteredUser(newUser); 29 | return this.CreatedResponse(newUser); 30 | }); 31 | 32 | Put("/{userId:int}", parameters => 33 | { 34 | int userId = parameters.userId; 35 | var updatedUser = this.Bind(); 36 | registerUsers[userId] = updatedUser; 37 | return updatedUser; 38 | }); 39 | } 40 | 41 | private dynamic CreatedResponse(LoyaltyProgramUser newUser) 42 | { 43 | return 44 | this.Negotiate 45 | .WithStatusCode(HttpStatusCode.Created) 46 | .WithHeader("Location", this.Request.Url.SiteBase + "/users/" + newUser.Id) 47 | .WithModel(newUser); 48 | } 49 | 50 | private void AddRegisteredUser(LoyaltyProgramUser newUser) 51 | { 52 | var userId = registerUsers.Count; 53 | newUser.Id = userId; 54 | registerUsers[userId] = newUser; 55 | } 56 | } 57 | 58 | public class LoyaltyProgramUser 59 | { 60 | public int Id { get; set; } 61 | public string Name { get; set; } 62 | public int LoyaltyPoints { get; set; } 63 | public LoyaltyProgramSettings Settings { get; set; } 64 | } 65 | 66 | public class LoyaltyProgramSettings 67 | { 68 | public string[] Interests { get; set; } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgram/YamlSerializerDeserializer.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using Nancy; 7 | using Nancy.ModelBinding; 8 | using Nancy.Responses.Negotiation; 9 | using YamlDotNet.Serialization; 10 | 11 | public class YamlBodyDeserializer : IBodyDeserializer 12 | { 13 | public bool CanDeserialize(MediaRange mediaRange, BindingContext context) 14 | => mediaRange.Subtype.ToString().EndsWith("yaml"); 15 | 16 | public object Deserialize(MediaRange mediaRange, Stream bodyStream, BindingContext context) 17 | { 18 | var yamlDeserializer = new Deserializer(); 19 | var reader = new StreamReader(bodyStream); 20 | return yamlDeserializer.Deserialize(reader, context.DestinationType); 21 | } 22 | } 23 | 24 | public class YamlBodySerializer : IResponseProcessor 25 | { 26 | public IEnumerable> ExtensionMappings 27 | { 28 | get 29 | { 30 | yield return new Tuple("yaml", new MediaRange("application/yaml")); 31 | } 32 | } 33 | 34 | public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context) 35 | => 36 | requestedMediaRange.Subtype.ToString().EndsWith("yaml") 37 | ? new ProcessorMatch { ModelResult = MatchResult.DontCare, RequestedContentTypeResult = MatchResult.NonExactMatch} 38 | : ProcessorMatch.None; 39 | 40 | public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context) 41 | => 42 | new Response 43 | { 44 | Contents = stream => 45 | { 46 | var yamlSerializer = new Serializer(); 47 | var streamWriter = new StreamWriter(stream); 48 | yamlSerializer.Serialize(streamWriter, model); 49 | streamWriter.Flush(); 50 | }, 51 | ContentType = "application/yaml" 52 | }; 53 | } 54 | } -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgram/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Nancy": "2.0.0-barneyrubble", 10 | "YamlDotNet": "3.8.0-pre233" 11 | }, 12 | 13 | "tools": { 14 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": { 15 | "version": "1.0.0-preview1-final", 16 | "imports": "portable-net45+win8+dnxcore50" 17 | } 18 | }, 19 | 20 | "frameworks": { 21 | "netcoreapp1.0": { 22 | "imports": [ 23 | "dotnet5.6", 24 | "portable-net45+win8" 25 | ] 26 | } 27 | }, 28 | 29 | "buildOptions": { 30 | "emitEntryPoint": true, 31 | "preserveCompilationContext": true 32 | }, 33 | 34 | "runtimeOptions": { 35 | "gcServer": true 36 | }, 37 | 38 | "publishOptions": { 39 | "include": [ 40 | "wwwroot", 41 | "web.config" 42 | ] 43 | }, 44 | 45 | "scripts": { 46 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 47 | }, 48 | 49 | "tooling": { 50 | "defaultNamespace": "LoyaltyProgram" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgram/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgramEventConsumer/LoyaltyProgramEventConsumer.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 47349717-585a-43ee-96e8-7d5249cb431d 10 | LoyaltyProgramEventConsumer 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter06/LoyaltyProgramEventConsumer/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "emitEntryPoint": true 5 | }, 6 | 7 | "dependencies": { 8 | "Newtonsoft.Json": "8.0.3", 9 | "System.ServiceProcess.ServiceController": "4.1.0", 10 | "System.Net.Http": "4.1.0" 11 | }, 12 | 13 | "frameworks": { 14 | "net461": { } 15 | }, 16 | 17 | "tooling": { 18 | "defaultNamespace": "LoyaltyProgramEventConsumer" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Chapter07/.idea.Ch7/riderModule.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram 2 | { 3 | using System; 4 | using Nancy; 5 | using Nancy.Bootstrapper; 6 | using Nancy.TinyIoc; 7 | 8 | public class Bootstrapper : DefaultNancyBootstrapper 9 | { 10 | protected override Func InternalConfiguration 11 | => NancyInternalConfiguration.WithOverrides(builder => builder.StatusCodeHandlers.Clear()); 12 | 13 | protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) 14 | { 15 | pipelines.OnError += (ctx, ex) => 16 | { 17 | // write to central log store 18 | return null; 19 | }; 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/EventFeed/Event.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram.EventFeed 2 | { 3 | using System; 4 | 5 | public struct Event 6 | { 7 | public long SequenceNumber { get; } 8 | public DateTimeOffset OccuredAt { get; } 9 | public string Name { get; } 10 | public object Content { get; } 11 | 12 | public Event( 13 | long sequenceNumber, 14 | DateTimeOffset occuredAt, 15 | string name, 16 | object content) 17 | { 18 | this.SequenceNumber = sequenceNumber; 19 | this.OccuredAt = occuredAt; 20 | this.Name = name; 21 | this.Content = content; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/EventFeed/EventStore.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram.EventFeed 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | 8 | public class EventStore : IEventStore 9 | { 10 | private static long currentSequenceNumber = 0; 11 | private static readonly IList database = new List(); 12 | 13 | public IEnumerable GetEvents( 14 | long firstEventSequenceNumber, 15 | long lastEventSequenceNumber) 16 | => database 17 | .Where(e => 18 | e.SequenceNumber >= firstEventSequenceNumber && 19 | e.SequenceNumber <= lastEventSequenceNumber) 20 | .OrderBy(e => e.SequenceNumber); 21 | 22 | public void Raise(string eventName, object content) 23 | { 24 | var seqNumber = Interlocked.Increment(ref currentSequenceNumber); 25 | database.Add( 26 | new Event( 27 | seqNumber, 28 | DateTimeOffset.UtcNow, 29 | eventName, 30 | content)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/EventFeed/EventsFeedModule.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram.EventFeed 2 | { 3 | using Nancy; 4 | 5 | public class EventsFeedModule : NancyModule 6 | { 7 | public EventsFeedModule(IEventStore eventStore) : base("/events") 8 | { 9 | Get("/", _ => 10 | { 11 | long firstEventSequenceNumber, lastEventSequenceNumber; 12 | if (!long.TryParse(this.Request.Query.start.Value, 13 | out firstEventSequenceNumber)) 14 | firstEventSequenceNumber = 0; 15 | if (!long.TryParse(this.Request.Query.end.Value, 16 | out lastEventSequenceNumber)) 17 | lastEventSequenceNumber = 50; 18 | 19 | return 20 | eventStore.GetEvents( 21 | firstEventSequenceNumber, 22 | lastEventSequenceNumber); 23 | }); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/EventFeed/IEventStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace LoyaltyProgram.EventFeed 4 | { 5 | public interface IEventStore 6 | { 7 | IEnumerable GetEvents(long firstEventSequenceNumber, long lastEventSequenceNumber); 8 | void Raise(string eventName, object content); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/LoyaltyProgram.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 4bed3c45-e8c3-4345-a08f-249a39a256eb 10 | LoyaltyProgram 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/Program.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram 2 | { 3 | using System.IO; 4 | using Microsoft.AspNetCore.Hosting; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var host = new WebHostBuilder() 11 | .UseKestrel() 12 | .UseContentRoot(Directory.GetCurrentDirectory()) 13 | .UseIISIntegration() 14 | .UseStartup() 15 | .Build(); 16 | 17 | host.Run(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "LoyaltyProgram": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram 2 | { 3 | using Microsoft.AspNetCore.Builder; 4 | using Nancy.Owin; 5 | 6 | public class Startup 7 | { 8 | public void Configure(IApplicationBuilder app) 9 | { 10 | app.UseOwin(buildFunc => buildFunc.UseNancy()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/UsersModule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Nancy; 3 | using Nancy.ModelBinding; 4 | 5 | namespace LoyaltyProgram 6 | { 7 | public class UsersModule : NancyModule 8 | { 9 | private static IDictionary registerUsers = 10 | new Dictionary(); 11 | 12 | public UsersModule() : base("/users") 13 | { 14 | Get("/", _ => registerUsers.Values); 15 | 16 | Get("/{userId:int}", parameters => 17 | { 18 | int userId = parameters.userId; 19 | if (registerUsers.ContainsKey(userId)) 20 | return registerUsers[userId]; 21 | else 22 | return HttpStatusCode.NotFound; 23 | }); 24 | 25 | Post("/", _ => 26 | { 27 | var newUser = this.Bind(); 28 | this.AddRegisteredUser(newUser); 29 | return this.CreatedResponse(newUser); 30 | }); 31 | 32 | Put("/{userId:int}", parameters => 33 | { 34 | int userId = parameters.userId; 35 | var updatedUser = this.Bind(); 36 | registerUsers[userId] = updatedUser; 37 | return updatedUser; 38 | }); 39 | } 40 | 41 | private dynamic CreatedResponse(LoyaltyProgramUser newUser) 42 | { 43 | return 44 | this.Negotiate 45 | .WithStatusCode(HttpStatusCode.Created) 46 | .WithHeader("Location", this.Request.Url.SiteBase + "/users/" + newUser.Id) 47 | .WithModel(newUser); 48 | } 49 | 50 | private void AddRegisteredUser(LoyaltyProgramUser newUser) 51 | { 52 | var userId = registerUsers.Count; 53 | newUser.Id = userId; 54 | registerUsers[userId] = newUser; 55 | } 56 | } 57 | 58 | public class LoyaltyProgramUser 59 | { 60 | public int Id { get; set; } 61 | public string Name { get; set; } 62 | public int LoyaltyPoints { get; set; } 63 | public LoyaltyProgramSettings Settings { get; set; } 64 | } 65 | 66 | public class LoyaltyProgramSettings 67 | { 68 | public string[] Interests { get; set; } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/YamlSerializerDeserializer.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using Nancy; 7 | using Nancy.ModelBinding; 8 | using Nancy.Responses.Negotiation; 9 | using YamlDotNet.Serialization; 10 | 11 | public class YamlBodyDeserializer : IBodyDeserializer 12 | { 13 | public bool CanDeserialize(MediaRange mediaRange, BindingContext context) 14 | => mediaRange.Subtype.ToString().EndsWith("yaml"); 15 | 16 | public object Deserialize(MediaRange mediaRange, Stream bodyStream, BindingContext context) 17 | { 18 | var yamlDeserializer = new Deserializer(); 19 | var reader = new StreamReader(bodyStream); 20 | return yamlDeserializer.Deserialize(reader, context.DestinationType); 21 | } 22 | } 23 | 24 | public class YamlBodySerializer : IResponseProcessor 25 | { 26 | public IEnumerable> ExtensionMappings 27 | { 28 | get 29 | { 30 | yield return new Tuple("yaml", new MediaRange("application/yaml")); 31 | } 32 | } 33 | 34 | public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context) 35 | => 36 | requestedMediaRange.Subtype.ToString().EndsWith("yaml") 37 | ? new ProcessorMatch { ModelResult = MatchResult.DontCare, RequestedContentTypeResult = MatchResult.NonExactMatch} 38 | : ProcessorMatch.None; 39 | 40 | public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context) 41 | => 42 | new Response 43 | { 44 | Contents = stream => 45 | { 46 | var yamlSerializer = new Serializer(); 47 | var streamWriter = new StreamWriter(stream); 48 | yamlSerializer.Serialize(streamWriter, model); 49 | streamWriter.Flush(); 50 | }, 51 | ContentType = "application/yaml" 52 | }; 53 | } 54 | } -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Microsoft.AspNetCore.Owin": "1.0.0", 10 | "Nancy": "2.0.0-barneyrubble", 11 | "YamlDotNet": "3.8.0-pre233" 12 | }, 13 | 14 | "tools": { 15 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 16 | }, 17 | 18 | "frameworks": { 19 | "netcoreapp1.0": { 20 | "imports": [ 21 | "dotnet5.6", 22 | "portable-net45+win8" 23 | ] 24 | } 25 | }, 26 | 27 | "buildOptions": { 28 | "emitEntryPoint": true, 29 | "preserveCompilationContext": true 30 | }, 31 | 32 | "runtimeOptions": { 33 | "gcServer": true 34 | }, 35 | 36 | "publishOptions": { 37 | "include": [ 38 | "wwwroot", 39 | "web.config" 40 | ] 41 | }, 42 | 43 | "scripts": { 44 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 45 | }, 46 | 47 | "tooling": { 48 | "defaultNamespace": "LoyaltyProgram" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgram/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgramEventConsumer/LoyaltyProgramEventConsumer.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 47349717-585a-43ee-96e8-7d5249cb431d 10 | LoyaltyProgramEventConsumer 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgramEventConsumer/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "emitEntryPoint": true 5 | }, 6 | 7 | "dependencies": { 8 | "Newtonsoft.Json": "8.0.3", 9 | "System.ServiceProcess.ServiceController": "4.1.0-rc2-24027", 10 | "System.Net.Http": "4.1.0" 11 | }, 12 | 13 | "frameworks": { 14 | "net461": { } 15 | }, 16 | 17 | "tooling": { 18 | "defaultNamespace": "LoyaltyProgramEventConsumer" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgramIntegrationTest/FakeEndpoints.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgramIntegrationTests 2 | { 3 | using System; 4 | using System.Threading; 5 | using Microsoft.AspNetCore.Builder; 6 | using Nancy; 7 | using Nancy.Owin; 8 | 9 | public class FakeEventFeed : NancyModule 10 | { 11 | public static AutoResetEvent polled = new AutoResetEvent(initialState: false); 12 | 13 | public FakeEventFeed() 14 | { 15 | this.Get("/events", _ => 16 | { 17 | polled.Set(); 18 | return Response.AsJson(new [] { new { SequenceNumber = 1, Name= "baz", Content = new { OfferName = "foo", Desciption = "bar", item = new { ProductName = "name" }}}}); 19 | }); 20 | } 21 | } 22 | 23 | public class FakeNotifications : NancyModule 24 | { 25 | public static bool NotificationWasSent = false; 26 | public FakeNotifications() 27 | { 28 | this.Get("/notify", _ => 29 | { 30 | NotificationWasSent = true; 31 | Console.WriteLine("notified"); 32 | return 200; 33 | }); 34 | } 35 | } 36 | 37 | public class FakeStartup 38 | { 39 | public void Configure(IApplicationBuilder app) 40 | { 41 | app.UseOwin(buildFunc => buildFunc.UseNancy()); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgramIntegrationTest/LoyaltyProgramIntegrationTest.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0.25123 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 4adb7532-5bbb-4a6f-9044-6095427db0e6 10 | LoyaltyProgramIntegrationTest 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgramIntegrationTest/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "buildOptions": { 5 | "preserveCompilationContext": true 6 | }, 7 | 8 | "dependencies": { 9 | "dotnet-test-xunit": "2.2.0-preview2-build1029", 10 | "Microsoft.NETCore.App": { 11 | "version": "1.0.0", 12 | "type": "platform" 13 | }, 14 | "xunit": "2.1.0", 15 | "Microsoft.AspNetCore.Owin": "1.0.0", 16 | "Nancy": "2.0.0-barneyrubble", 17 | "LoyaltyProgram": { "target": "project" } 18 | }, 19 | 20 | "frameworks": { 21 | "netcoreapp1.0": { 22 | "imports": [ 23 | "dotnet5.6", 24 | "portable-net45+win8" 25 | ] 26 | } 27 | }, 28 | 29 | "testRunner": "xunit", 30 | 31 | "tooling": { 32 | "defaultNamespace": "LoyaltyProgramIntegrationTest" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgramUnitTests/LoyaltyProgramUnitTests.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0.25123 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | df4d91a9-c2b2-47a1-b3f5-74d3a03e7aaf 10 | LoyaltyProgramUnitTests 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgramUnitTests/TestModule_should.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgramUnitTests 2 | { 3 | using System.Threading.Tasks; 4 | using Nancy; 5 | using Nancy.Testing; 6 | using Xunit; 7 | 8 | public class TestModule_should 9 | { 10 | public class TestModule : NancyModule 11 | { 12 | public TestModule() 13 | { 14 | Get("/", _ => 200); 15 | } 16 | } 17 | 18 | [Fact] 19 | public async Task respond_ok_to_request_to_root() 20 | { 21 | var sut = new Browser(with => with.Module()); 22 | var actual = await sut.Get("/"); 23 | 24 | Assert.Equal(HttpStatusCode.OK, actual.StatusCode); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgramUnitTests/UserModule_should.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgramUnitTests 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | using LoyaltyProgram; 6 | using Nancy; 7 | using Nancy.Testing; 8 | using Xunit; 9 | 10 | public class UserModule_should 11 | { 12 | private Browser sut; 13 | 14 | public UserModule_should() 15 | { 16 | this.sut = new Browser( 17 | new Bootstrapper(), 18 | defaultsTo => defaultsTo.Accept("application/json")); 19 | } 20 | 21 | [Fact] 22 | public async Task respond_not_when_queried_for_unregistered_user() 23 | { 24 | var actual = await sut.Get("/users/1000"); 25 | Assert.Equal(HttpStatusCode.NotFound, actual.StatusCode); 26 | } 27 | 28 | [Fact] 29 | public async Task allow_to_register_new_user() 30 | { 31 | var expected = new LoyaltyProgramUser() {Name = "Chr"}; 32 | var registrationResponse = await sut.Post("/users", with => with.JsonBody(expected)); 33 | var newUser = registrationResponse.Body.DeserializeJson(); 34 | 35 | var actual = await sut.Get($"/users/{newUser.Id}"); 36 | 37 | Assert.Equal(HttpStatusCode.OK, actual.StatusCode); 38 | Assert.Equal(expected.Name, actual.Body.DeserializeJson().Name); 39 | } 40 | 41 | [Fact] 42 | public async Task allow_modififying_users() 43 | { 44 | var expected = "jane"; 45 | var user = new LoyaltyProgramUser() {Name = "Chr"}; 46 | var registrationResponse = await sut.Post("/users", with => with.JsonBody(user)); 47 | var newUser = registrationResponse.Body.DeserializeJson(); 48 | 49 | newUser.Name = expected; 50 | var actual = await sut.Put($"/users/{newUser.Id}", with => with.JsonBody(newUser)); 51 | 52 | Assert.Equal(expected, actual.Body.DeserializeJson().Name); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Chapter07/LoyaltyProgramUnitTests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "buildOptions": { 5 | "preserveCompilationContext": true 6 | }, 7 | 8 | "dependencies": { 9 | "dotnet-test-xunit": "2.2.0-preview2-build1029", 10 | "Microsoft.NETCore.App": { 11 | "version": "1.0.0", 12 | "type": "platform" 13 | }, 14 | "xunit": "2.1.0", 15 | "Nancy.Testing": "2.0.0-barneyrubble", 16 | "LoyaltyProgram": {"target": "project"} 17 | 18 | }, 19 | 20 | "frameworks": { 21 | "netcoreapp1.0": { 22 | "imports": [ 23 | "dotnet5.6", 24 | "portable-net45+win8" 25 | ] 26 | } 27 | }, 28 | 29 | "testRunner": "xunit", 30 | 31 | "tooling": { 32 | "defaultNamespace": "LoyaltyProgramUnitTests" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (console)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "stopAtEntry": false 13 | }, 14 | { 15 | "name": ".NET Core Launch (web)", 16 | "type": "coreclr", 17 | "request": "launch", 18 | "preLaunchTask": "build", 19 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", 20 | "args": [], 21 | "cwd": "${workspaceRoot}", 22 | "stopAtEntry": false, 23 | "launchBrowser": { 24 | "enabled": true, 25 | "args": "${auto-detect-url}", 26 | "windows": { 27 | "command": "cmd.exe", 28 | "args": "/C start ${auto-detect-url}" 29 | }, 30 | "osx": { 31 | "command": "open" 32 | }, 33 | "linux": { 34 | "command": "xdg-open" 35 | } 36 | } 37 | }, 38 | { 39 | "name": ".NET Core Attach", 40 | "type": "coreclr", 41 | "request": "attach", 42 | "processName": "" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [], 10 | "isBuildCommand": true, 11 | "problemMatcher": "$msCompile" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/EventFeed/Event.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using System; 4 | 5 | public struct Event 6 | { 7 | public long SequenceNumber { get; } 8 | public DateTimeOffset OccuredAt { get; } 9 | public string Name { get; } 10 | public object Content { get; } 11 | 12 | public Event( 13 | long sequenceNumber, 14 | DateTimeOffset occuredAt, 15 | string name, 16 | object content) 17 | { 18 | this.SequenceNumber = sequenceNumber; 19 | this.OccuredAt = occuredAt; 20 | this.Name = name; 21 | this.Content = content; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/EventFeed/EventStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | #if net461 8 | using global::EventStore.ClientAPI; 9 | #endif 10 | public class EventStore : IEventStore 11 | { 12 | private static long currentSequenceNumber = 0; 13 | private static readonly IList database = new List(); 14 | #if net461 15 | private const string connectionString = 16 | "ConnectTo=discover://admin:changeit@127.0.0.1:2112/"; 17 | private IEventStoreConnection connection = 18 | EventStoreConnection.Create(connectionString); 19 | #endif 20 | public IEnumerable GetEvents( 21 | long firstEventSequenceNumber, 22 | long lastEventSequenceNumber) 23 | => database 24 | .Where(e => 25 | e.SequenceNumber >= firstEventSequenceNumber && 26 | e.SequenceNumber <= lastEventSequenceNumber) 27 | .OrderBy(e => e.SequenceNumber); 28 | 29 | public void Raise(string eventName, object content) 30 | { 31 | var seqNumber = Interlocked.Increment(ref currentSequenceNumber); 32 | database.Add( 33 | new Event( 34 | seqNumber, 35 | DateTimeOffset.UtcNow, 36 | eventName, 37 | content)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/EventFeed/EventsFeedModule.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using Nancy; 4 | 5 | public class EventsFeedModule : NancyModule 6 | { 7 | public EventsFeedModule(IEventStore eventStore) : base("/events") 8 | { 9 | Get("/", _ => 10 | { 11 | long firstEventSequenceNumber, lastEventSequenceNumber; 12 | if (!long.TryParse(this.Request.Query.start.Value, 13 | out firstEventSequenceNumber)) 14 | firstEventSequenceNumber = 0; 15 | if (!long.TryParse(this.Request.Query.end.Value, 16 | out lastEventSequenceNumber)) 17 | lastEventSequenceNumber = long.MaxValue; 18 | 19 | return 20 | eventStore.GetEvents( 21 | firstEventSequenceNumber, 22 | lastEventSequenceNumber); 23 | }); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/EventFeed/IEventStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using ShoppingCart.ShoppingCart; 3 | 4 | namespace ShoppingCart.EventFeed 5 | { 6 | public interface IEventStore 7 | { 8 | IEnumerable GetEvents(long firstEventSequenceNumber, long lastEventSequenceNumber); 9 | void Raise(string eventName, object content); 10 | } 11 | } -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/ICache.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | 7 | public interface ICache 8 | { 9 | void Add(string key, object value, TimeSpan ttl); 10 | object Get(string productsResource); 11 | } 12 | 13 | public class Cache : ICache 14 | { 15 | private static IDictionary> cache = new ConcurrentDictionary>(); 16 | 17 | public void Add(string key, object value, TimeSpan ttl) 18 | { 19 | cache[key] = Tuple.Create(DateTimeOffset.UtcNow.Add(ttl), value); 20 | } 21 | 22 | public object Get(string productsResource) 23 | { 24 | Tuple value; 25 | if (cache.TryGetValue(productsResource, out value) && value.Item1 > DateTimeOffset.UtcNow) 26 | return value; 27 | cache.Remove(productsResource); 28 | return null; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/IProductCatalogueClient.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using ShoppingCart; 6 | 7 | public interface IProductCatalogueClient 8 | { 9 | Task> 10 | GetShoppingCartItems(int[] productCatalogueIds); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/MonitoringMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.Infrastructure 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | using LibOwin; 7 | 8 | using AppFunc = System.Func, System.Threading.Tasks.Task>; 9 | 10 | public class MonitoringMiddleware 11 | { 12 | private AppFunc next; 13 | private Func> healthCheck; 14 | 15 | private static readonly PathString monitorPath = new PathString("/_monitor"); 16 | private static readonly PathString monitorShallowPath = new PathString("/_monitor/shallow"); 17 | private static readonly PathString monitorDeepPath = new PathString("/_monitor/deep"); 18 | 19 | public MonitoringMiddleware(AppFunc next, Func> healthCheck) 20 | { 21 | this.next = next; 22 | this.healthCheck = healthCheck; 23 | } 24 | 25 | public Task Invoke(IDictionary env) 26 | { 27 | var context = new OwinContext(env); 28 | if (context.Request.Path.StartsWithSegments(monitorPath)) 29 | return HandleMonitorEndpoint(context); 30 | else 31 | return this.next(env); 32 | } 33 | 34 | private Task HandleMonitorEndpoint(OwinContext context) 35 | { 36 | if (context.Request.Path.StartsWithSegments(monitorShallowPath)) 37 | return ShallowEndpoint(context); 38 | else if (context.Request.Path.StartsWithSegments(monitorDeepPath)) 39 | return DeepEndpoint(context); 40 | return Task.FromResult(0); 41 | } 42 | 43 | private async Task DeepEndpoint(OwinContext context) 44 | { 45 | if (await this.healthCheck()) 46 | context.Response.StatusCode = 204; 47 | else 48 | context.Response.StatusCode = 503; 49 | } 50 | 51 | private Task ShallowEndpoint(OwinContext context) 52 | { 53 | context.Response.StatusCode = 204; 54 | return Task.FromResult(0); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace ShoppingCart 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var host = new WebHostBuilder() 11 | .UseKestrel() 12 | .UseContentRoot(Directory.GetCurrentDirectory()) 13 | .UseIISIntegration() 14 | .UseStartup() 15 | .Build(); 16 | 17 | host.Run(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "ShoppingCart": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/ShoppingCart.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10e4f601-c16b-4936-a7b4-d32d799318d1 10 | ShoppingCart 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/ShoppingCart/IShoppingCartStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | public interface IShoppingCartStore 4 | { 5 | ShoppingCart Get(int userId); 6 | void Save(ShoppingCart shoppingCart); 7 | } 8 | } -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/ShoppingCart/ShoppingCartModule.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | using EventFeed; 4 | using Nancy; 5 | using Nancy.ModelBinding; 6 | 7 | public class ShoppingCartModule : NancyModule 8 | { 9 | public ShoppingCartModule( 10 | IShoppingCartStore shoppingCartStore, 11 | IProductCatalogueClient productCatalogue, 12 | IEventStore eventStore) 13 | : base("/shoppingcart") 14 | { 15 | Get("/{userid:int}", parameters => 16 | { 17 | var userId = (int) parameters.userid; 18 | return shoppingCartStore.Get(userId); 19 | }); 20 | 21 | Post("/{userid:int}/items", async (parameters, _) => 22 | { 23 | var productCatalogueIds = this.Bind(); 24 | var userId = (int) parameters.userid; 25 | 26 | var shoppingCart = shoppingCartStore.Get(userId); 27 | var shoppingCartItems = await productCatalogue.GetShoppingCartItems(productCatalogueIds).ConfigureAwait(false); 28 | shoppingCart.AddItems(shoppingCartItems, eventStore); 29 | shoppingCartStore.Save(shoppingCart); 30 | 31 | return shoppingCart; 32 | }); 33 | 34 | Delete("/{userid:int}/items", parameters => 35 | { 36 | var productCatalogueIds = this.Bind(); 37 | var userId = (int)parameters.userid; 38 | 39 | var shoppingCart = shoppingCartStore.Get(userId); 40 | shoppingCart.RemoveItems(productCatalogueIds, eventStore); 41 | shoppingCartStore.Save(shoppingCart); 42 | 43 | return shoppingCart; 44 | }); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/ShoppingCart/ShoppingCartStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | using System.Collections.Generic; 4 | 5 | public class ShoppingCartStore : IShoppingCartStore 6 | { 7 | private static readonly Dictionary database = new Dictionary(); 8 | 9 | public ShoppingCart Get(int userId) 10 | { 11 | if (!database.ContainsKey(userId)) 12 | database[userId] = new ShoppingCart(userId); 13 | return database[userId]; 14 | } 15 | 16 | public void Save(ShoppingCart shoppingCart) 17 | { 18 | // Nothing needed. Saving would be needed with a real DB 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/database-scripts/create-shopping-cart-db.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE ShoppingCart 2 | GO 3 | 4 | USE [ShoppingCart] 5 | GO 6 | 7 | CREATE TABLE [dbo].[ShoppingCart]( 8 | [ID] int IDENTITY(1,1) PRIMARY KEY, 9 | [UserId] [bigint] NOT NULL, 10 | CONSTRAINT ShoppingCartUnique UNIQUE([ID], [UserID]) 11 | ) 12 | GO 13 | 14 | CREATE INDEX ShoppingCart_UserId 15 | ON [dbo].[ShoppingCart] (UserId) 16 | GO 17 | 18 | CREATE TABLE [dbo].[ShoppingCartItems]( 19 | [ID] int IDENTITY(1,1) PRIMARY KEY, 20 | [ShoppingCartId] [int] NOT NULL, 21 | [ProductCatalogId] [bigint] NOT NULL, 22 | [ProductName] [nvarchar](100) NOT NULL, 23 | [ProductDescription] [nvarchar](500) NULL, 24 | [Amount] [int] NOT NULL, 25 | [Currency] [nvarchar](5) NOT NULL 26 | ) 27 | 28 | GO 29 | 30 | ALTER TABLE [dbo].[ShoppingCartItems] WITH CHECK ADD CONSTRAINT [FK_ShoppingCart] FOREIGN KEY([ShoppingCartId]) 31 | REFERENCES [dbo].[ShoppingCart] ([Id]) 32 | GO 33 | 34 | ALTER TABLE [dbo].[ShoppingCartItems] CHECK CONSTRAINT [FK_ShoppingCart] 35 | GO 36 | 37 | CREATE INDEX ShoppingCartItems_ShoppingCartId 38 | ON [dbo].[ShoppingCartItems] (ShoppingCartId) 39 | GO 40 | 41 | CREATE TABLE [dbo].[EventStore]( 42 | [ID] int IDENTITY(1,1) PRIMARY KEY, 43 | [Name] [nvarchar](100) NOT NULL, 44 | [OccuredAt] [datetimeoffset] NOT NULL, 45 | [Content][nvarchar](max) NOT NULL 46 | ) 47 | GO 48 | 49 | -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 4 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 5 | "Microsoft.AspNetCore.Owin": "1.0.0", 6 | "Nancy": "2.0.0-barneyrubble", 7 | "Polly": "4.2.1", 8 | "Dapper": "1.50.0-rc2a", 9 | "Serilog": "2.0.0-rc-600", 10 | "Serilog.Sinks.ColoredConsole": "2.0.0-beta-1001" 11 | }, 12 | "runtimes": { 13 | "win10-x64": "" 14 | }, 15 | 16 | "tools": { 17 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 18 | }, 19 | 20 | "frameworks": { 21 | "netcoreapp1.0": { 22 | "imports": [ 23 | "dotnet5.6", 24 | "portable-net45+win8" 25 | ], 26 | "dependencies": { 27 | "System.Net.Http": "4.0.1-rc2-24027" 28 | } 29 | }, 30 | "net461": { 31 | "buildOptions": {"define": ["net461"]}, 32 | "frameworkAssemblies": { 33 | "System.Net.Http": "4.0.0.0", 34 | "System.Runtime": "4.0.20.0" 35 | }, 36 | "dependencies": { 37 | "EventStore.Client": "3.3.1", 38 | "Microsoft.CSharp": "4.0.0" 39 | } 40 | } 41 | }, 42 | 43 | "buildOptions": { 44 | "emitEntryPoint": true, 45 | "preserveCompilationContext": true 46 | }, 47 | 48 | "runtimeOptions": { 49 | "gcServer": true 50 | }, 51 | 52 | "publishOptions": { 53 | "include": [ 54 | "wwwroot", 55 | "web.config" 56 | ] 57 | }, 58 | 59 | "scripts": { 60 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 61 | }, 62 | 63 | "tooling": { 64 | "defaultNamespace": "HelloMicroservices" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Chapter09/ShoppingCart/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter11/.idea.ch11/riderModule.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Chapter11/HelloMicroservicesPlatform/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/Chapter11/HelloMicroservicesPlatform/HelloMicroservicesPlatform.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0.25123 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | daebd923-b213-4ca5-a240-b96686aec974 10 | HelloMicroservicesPlatform 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter11/HelloMicroservicesPlatform/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace HelloMicroservicesPlatform 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .Build(); 20 | 21 | host.Run(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Chapter11/HelloMicroservicesPlatform/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "HelloMicroservicesPlatform": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter11/HelloMicroservicesPlatform/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace HelloMicroservicesPlatform 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using MicroserviceNET.Logging; 7 | using MicroserviceNET.Auth; 8 | using MicroserviceNET.Platform; 9 | using Serilog; 10 | using Serilog.Events; 11 | using Nancy; 12 | using Nancy.Owin; 13 | using Nancy.TinyIoc; 14 | using Nancy.Bootstrapper; 15 | 16 | public class Startup 17 | { 18 | public void Configure(IApplicationBuilder app) 19 | { 20 | app.UseOwin() 21 | .UseMonitoringAndLogging(ConfigureLogger(), HealthCheck) 22 | .UseAuthPlatform("test-scope") 23 | .UseNancy(); 24 | } 25 | 26 | 27 | private ILogger ConfigureLogger() 28 | { 29 | return new LoggerConfiguration() 30 | .Enrich.FromLogContext() 31 | .WriteTo.ColoredConsole( 32 | LogEventLevel.Verbose, 33 | "{NewLine}{Timestamp:HH:mm:ss} [{Level}] ({CorrelationToken}) {Message}{NewLine}{Exception}") 34 | .CreateLogger(); 35 | } 36 | 37 | private static Task HealthCheck() 38 | { 39 | return Task.FromResult(true); 40 | } 41 | } 42 | 43 | public class Bootstrapper : DefaultNancyBootstrapper 44 | { 45 | protected override void RequestStartup( 46 | TinyIoCContainer container, 47 | IPipelines pipelines, 48 | NancyContext context) 49 | { 50 | base.RequestStartup(container, pipelines, context); 51 | container.UseHttpClientFactory(context); 52 | } 53 | } 54 | 55 | public class Hello : NancyModule 56 | { 57 | public Hello(IHttpClientFactory clientFactory) 58 | { 59 | Get("/", async (_, __) => 60 | { 61 | var client = await 62 | clientFactory.Create( 63 | new Uri("http://otherservice/"), 64 | "scope_for_other_microservice"); 65 | var resp = await 66 | client.GetAsync("/some/path").ConfigureAwait(false); 67 | return resp.StatusCode; 68 | }); 69 | } 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/Chapter11/HelloMicroservicesPlatform/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Microsoft.AspNetCore.Owin": "1.0.0", 10 | "Serilog": "2.0.0-rc-600", 11 | "MicroserviceNET.Platform": { "target": "project" }, 12 | "Serilog.Sinks.ColoredConsole": "2.0.0-beta-700" 13 | }, 14 | 15 | "tools": { 16 | }, 17 | 18 | "frameworks": { 19 | "netcoreapp1.0": { 20 | "imports": [ 21 | "dotnet5.6", 22 | "portable-net45+win8" 23 | ] 24 | } 25 | }, 26 | 27 | "buildOptions": { 28 | "emitEntryPoint": true, 29 | "preserveCompilationContext": true 30 | }, 31 | 32 | "runtimeOptions": { 33 | "gcServer": true 34 | }, 35 | 36 | "publishOptions": { 37 | "include": [ 38 | "wwwroot", 39 | "web.config" 40 | ] 41 | }, 42 | 43 | "scripts": { 44 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 45 | }, 46 | 47 | "tooling": { 48 | "defaultNamespace": "HelloMicroservicesPlatform" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Chapter11/HelloMicroservicesPlatform/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter11/MicroserivceNET.Auth/AuthorizationMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace MicroserviceNET.Auth 2 | { 3 | using System.IdentityModel.Tokens.Jwt; 4 | using System.Threading.Tasks; 5 | using LibOwin; 6 | using Microsoft.IdentityModel.Tokens; 7 | 8 | using AppFunc = System.Func, System.Threading.Tasks.Task>; 9 | 10 | public class Authorization 11 | { 12 | public static AppFunc Middleware(AppFunc next, string requiredScope) 13 | { 14 | return env => 15 | { 16 | var ctx = new OwinContext(env); 17 | var principal = ctx.Request.User; 18 | if (principal.HasClaim("scope", requiredScope)) 19 | return next(env); 20 | ctx.Response.StatusCode = 403; 21 | return Task.FromResult(0); 22 | }; 23 | } 24 | } 25 | 26 | public class IdToken 27 | { 28 | public static AppFunc Middleware(AppFunc next) 29 | { 30 | return env => 31 | { 32 | var ctx = new OwinContext(env); 33 | if (ctx.Request.Headers.ContainsKey("microservice.NET-end-user")) 34 | { 35 | var tokenHandler = new JwtSecurityTokenHandler(); 36 | SecurityToken token; 37 | var userPrincipal = 38 | tokenHandler.ValidateToken(ctx.Request.Headers["microservice.NET-end-user"], 39 | new TokenValidationParameters(), out token); 40 | ctx.Set("pos-end-user", userPrincipal); 41 | } 42 | return next(env); 43 | }; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Chapter11/MicroserivceNET.Auth/BuildFuncExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace MicroserviceNET.Auth 2 | { 3 | using BuildFunc = System.Action, System.Threading.Tasks.Task>, 5 | System.Func, System.Threading.Tasks.Task>>>; 6 | 7 | public static class BuildFuncExtensions 8 | { 9 | public static BuildFunc UseAuthPlatform(this BuildFunc buildFunc, string requiredScope) 10 | { 11 | buildFunc(next => Authorization.Middleware(next, requiredScope)); 12 | buildFunc(next => IdToken.Middleware(next)); 13 | return buildFunc; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Chapter11/MicroserivceNET.Auth/MicroserivceNET.Auth.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0.25123 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 662415b4-3486-4b64-b1f9-718b9ff0879d 10 | MicroserivceNET.Auth 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter11/MicroserivceNET.Auth/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "dependencies": { 5 | "NETStandard.Library": "1.5.0", 6 | "Nancy": "2.0.0-barneyrubble", 7 | "System.IdentityModel.Tokens.Jwt": "5.0.0-rc2-305061149", 8 | "System.Security.Principal": "4.0.1", 9 | "System.Security.Claims": "4.0.1", 10 | "System.Globalization.Extensions": "4.0.1" 11 | }, 12 | 13 | "frameworks": { 14 | "netstandard1.5": { 15 | "imports": "dnxcore50" 16 | } 17 | }, 18 | 19 | "tooling": { 20 | "defaultNamespace": "MicroserivceNET.Auth" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Chapter11/MicroserviceNET.Logging/BuildFuncExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace MicroserviceNET.Logging 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | using Serilog; 6 | 7 | using BuildFunc = System.Action, System.Threading.Tasks.Task>, 9 | System.Func, System.Threading.Tasks.Task>>>; 10 | 11 | public static class BuildFuncExtensions 12 | { 13 | public static BuildFunc UseMonitoringAndLogging( 14 | this BuildFunc buildFunc, 15 | ILogger log, 16 | Func> healthCheck) 17 | { 18 | buildFunc(next => GlobalErrorLogging.Middleware(next, log)); 19 | buildFunc(next => CorrelationToken.Middleware(next)); 20 | buildFunc(next => RequestLogging.Middleware(next, log)); 21 | buildFunc(next => PerformanceLogging.Middleware(next, log)); 22 | buildFunc(next => new MonitoringMiddleware(next, healthCheck).Invoke); 23 | return buildFunc; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Chapter11/MicroserviceNET.Logging/MicroserviceNET.Logging.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0.25123 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | f73df2ab-708b-4916-ac28-e95117466511 10 | MicroserviceNET.Logging 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter11/MicroserviceNET.Logging/MonitoringMiddleware.cs: -------------------------------------------------------------------------------- 1 | namespace MicroserviceNET.Logging 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | using LibOwin; 7 | 8 | using AppFunc = System.Func, System.Threading.Tasks.Task>; 9 | 10 | public class MonitoringMiddleware 11 | { 12 | private readonly AppFunc next; 13 | private readonly Func> healthCheck; 14 | 15 | private static readonly PathString monitorPath = new PathString("/_monitor"); 16 | private static readonly PathString monitorShallowPath = new PathString("/_monitor/shallow"); 17 | private static readonly PathString monitorDeepPath = new PathString("/_monitor/deep"); 18 | 19 | public MonitoringMiddleware(AppFunc next, Func> healthCheck) 20 | { 21 | this.next = next; 22 | this.healthCheck = healthCheck; 23 | } 24 | 25 | public Task Invoke(IDictionary env) 26 | { 27 | var context = new OwinContext(env); 28 | if (context.Request.Path.StartsWithSegments(monitorPath)) 29 | return HandleMonitorEndpoint(context); 30 | else 31 | return this.next(env); 32 | } 33 | 34 | private Task HandleMonitorEndpoint(OwinContext context) 35 | { 36 | if (context.Request.Path.StartsWithSegments(monitorShallowPath)) 37 | return ShallowEndpoint(context); 38 | else if (context.Request.Path.StartsWithSegments(monitorDeepPath)) 39 | return DeepEndpoint(context); 40 | return Task.FromResult(0); 41 | } 42 | 43 | private async Task DeepEndpoint(OwinContext context) 44 | { 45 | if (await this.healthCheck().ConfigureAwait(false)) 46 | context.Response.StatusCode = 204; 47 | else 48 | context.Response.StatusCode = 503; 49 | } 50 | 51 | private Task ShallowEndpoint(OwinContext context) 52 | { 53 | context.Response.StatusCode = 204; 54 | return Task.FromResult(0); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Chapter11/MicroserviceNET.Logging/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "dependencies": { 5 | "NETStandard.Library": "1.5.0", 6 | "Serilog": "2.0.0-rc-600", 7 | "System.Security.Principal": "4.0.1", 8 | "System.Security.Claims": "4.0.1", 9 | "System.Globalization.Extensions": "4.0.1" 10 | }, 11 | 12 | "frameworks": { 13 | "netstandard1.5": { 14 | "imports": "dnxcore50", 15 | "dependencies": { 16 | } 17 | } 18 | }, 19 | 20 | "tooling": { 21 | "defaultNamespace": "MicroserviceNET.Logging" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Chapter11/MicroserviceNET.Platform/HttpClientFactory.cs: -------------------------------------------------------------------------------- 1 | namespace MicroserviceNET.Platform 2 | { 3 | using System; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Threading.Tasks; 7 | using IdentityModel.Client; 8 | 9 | public interface IHttpClientFactory 10 | { 11 | Task Create(Uri uri, string requestScope); 12 | } 13 | 14 | public class HttpClientFactory : IHttpClientFactory 15 | { 16 | private readonly TokenClient tokenClient; 17 | private readonly string correlationToken; 18 | private readonly string idToken; 19 | 20 | public HttpClientFactory(string tokenUrl, string clientName, string clientSecret, string correlationToken, string idToken) 21 | { 22 | this.tokenClient = new TokenClient(tokenUrl, clientName, clientSecret); 23 | this.correlationToken = correlationToken; 24 | this.idToken = idToken; 25 | } 26 | 27 | public async Task Create(Uri uri, string requestScope) 28 | { 29 | var response = await this.tokenClient.RequestClientCredentialsAsync(requestScope).ConfigureAwait(false); 30 | var client = new HttpClient() { BaseAddress = uri }; 31 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", response.AccessToken); 32 | client.DefaultRequestHeaders.Add("Correlation-Token", this.correlationToken); 33 | if (!string.IsNullOrEmpty(this.idToken)) 34 | client.DefaultRequestHeaders.Add("microservice.NET-end-user", this.idToken); 35 | return client; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Chapter11/MicroserviceNET.Platform/MicroserviceNET.Platform.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0.25123 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 927b3235-8756-4b92-99df-4f8d8adae645 10 | MicroserviceNET.Platform 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter11/MicroserviceNET.Platform/MicroservicePlatformHelper.cs: -------------------------------------------------------------------------------- 1 | namespace MicroserviceNET.Platform 2 | { 3 | using System.Security.Claims; 4 | using Nancy; 5 | using Nancy.Owin; 6 | using Nancy.TinyIoc; 7 | using LibOwin; 8 | 9 | public static class MicroservicePlatform 10 | { 11 | private static string TokenUrl; 12 | private static string ClientName; 13 | private static string ClientSecret; 14 | 15 | public static void Configure(string tokenUrl, string clientName, string clientSecret) 16 | { 17 | TokenUrl = tokenUrl; 18 | ClientName = clientName; 19 | ClientSecret = clientSecret; 20 | } 21 | 22 | public static TinyIoCContainer UseHttpClientFactory(this TinyIoCContainer self, NancyContext context) 23 | { 24 | var correlationToken = 25 | context.GetOwinEnvironment()?["correlationToken"] as string; 26 | object key = null; 27 | context.GetOwinEnvironment()?.TryGetValue(OwinConstants.RequestUser, out key); 28 | var principal = key as ClaimsPrincipal; 29 | var idToken = principal?.FindFirst("id_token"); 30 | self.Register(new HttpClientFactory(TokenUrl, ClientName, ClientSecret, correlationToken ?? "", idToken?.Value ?? "")); 31 | return self; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Chapter11/MicroserviceNET.Platform/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "dependencies": { 5 | "NETStandard.Library": "1.5.0", 6 | "MicroserivceNET.Auth": { 7 | "target": "project" 8 | }, 9 | "MicroserviceNET.Logging": { 10 | "target": "project", 11 | "version": "1.0.0" 12 | }, 13 | "Nancy": "2.0.0-barneyrubble", 14 | "IdentityModel": "2.0.0-beta5" 15 | }, 16 | 17 | "frameworks": { 18 | "netstandard1.5": { 19 | "imports": "dnxcore50" 20 | } 21 | }, 22 | 23 | "tooling": { 24 | "defaultNamespace": "MicroserviceNET.Platform" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Chapter12/.idea.C12/riderModule.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Chapter12/ApiGateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:latest 2 | 3 | COPY . /app 4 | 5 | WORKDIR /app 6 | 7 | RUN ["dotnet", "restore"] 8 | 9 | RUN ["dotnet", "build"] 10 | 11 | EXPOSE 5000/tcp 12 | 13 | ENTRYPOINT ["dotnet", "run", "--server.urls", "http://0.0.0.0:5000"] 14 | -------------------------------------------------------------------------------- /src/Chapter12/ApiGateway/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace ApiGateway 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | var config = new ConfigurationBuilder() 12 | .AddEnvironmentVariables(prefix: "ASPNETCORE_") 13 | .Build(); 14 | 15 | var host = new WebHostBuilder() 16 | .UseConfiguration(config) 17 | .UseKestrel() 18 | .UseContentRoot(Directory.GetCurrentDirectory()) 19 | .UseIISIntegration() 20 | .UseStartup() 21 | .Build(); 22 | 23 | host.Run(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Chapter12/ApiGateway/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ApiGateway": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "http://localhost:5000", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Chapter12/ApiGateway/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Microsoft.AspNetCore.Owin": "1.0.0", 10 | "Microsoft.AspNetCore.StaticFiles": "1.0.0", 11 | "System.Text.Encoding": "4.0.11", 12 | "Nancy": "2.0.0-barneyrubble", 13 | "MicroserviceNET.Platform": { "target": "project" }, 14 | "Serilog.Sinks.ColoredConsole": "2.0.0-beta-700" 15 | }, 16 | 17 | "tools": { 18 | }, 19 | 20 | "frameworks": { 21 | "netcoreapp1.0": { 22 | "imports": [ 23 | "dotnet5.6", 24 | "portable-net45+win8" 25 | ] 26 | } 27 | }, 28 | 29 | "buildOptions": { 30 | "emitEntryPoint": true, 31 | "preserveCompilationContext": true 32 | }, 33 | 34 | "runtimeOptions": { 35 | "configProperties": { 36 | "System.GC.Server": true 37 | } 38 | }, 39 | 40 | "publishOptions": { 41 | "include": [ 42 | "wwwroot", 43 | "web.config" 44 | ] 45 | }, 46 | 47 | "scripts": { 48 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 49 | }, 50 | 51 | "tooling": { 52 | "defaultNamespace": "ApiGateway" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Chapter12/ApiGateway/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter12/Login/Configuration/Clients.cs: -------------------------------------------------------------------------------- 1 | namespace Login.Configuration 2 | { 3 | using System.Collections.Generic; 4 | using IdentityServer4.Models; 5 | 6 | public class Clients 7 | { 8 | public static IEnumerable Get() => 9 | new List 10 | { 11 | new Client 12 | { 13 | ClientName = "API Gateway", 14 | ClientId = "api_gateway", 15 | ClientSecrets = new List 16 | { 17 | new Secret("secret".Sha256()) 18 | }, 19 | AllowedScopes = new List 20 | { 21 | "loyalty_program_write", 22 | }, 23 | AllowedGrantTypes = GrantTypes.ClientCredentials 24 | }, 25 | 26 | new Client 27 | { 28 | ClientName = "Web Client", 29 | ClientId = "web", 30 | 31 | RedirectUris = new List 32 | { 33 | "http://localhost:5003/signin-oidc", 34 | }, 35 | PostLogoutRedirectUris = new List 36 | { 37 | "http://localhost:5003/", 38 | }, 39 | 40 | AllowedScopes = new List 41 | { 42 | "openid", 43 | "email", 44 | "profile", 45 | } 46 | } 47 | }; 48 | } 49 | } -------------------------------------------------------------------------------- /src/Chapter12/Login/Configuration/Scopes.cs: -------------------------------------------------------------------------------- 1 | namespace Login.Configuration 2 | { 3 | using System.Collections.Generic; 4 | using IdentityServer4.Models; 5 | 6 | public class Scopes 7 | { 8 | public static IEnumerable Get() => 9 | new[] 10 | { 11 | // standard OpenID Connect scopes 12 | StandardScopes.OpenId, 13 | StandardScopes.ProfileAlwaysInclude, 14 | StandardScopes.EmailAlwaysInclude, 15 | new Scope 16 | { 17 | Name = "loyalty_program_write", 18 | DisplayName = "Loyalty Program write access", 19 | Type = ScopeType.Resource, 20 | } 21 | }; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Chapter12/Login/Configuration/Users.cs: -------------------------------------------------------------------------------- 1 | namespace Login.Configuration 2 | { 3 | using System.Collections.Generic; 4 | using System.Security.Claims; 5 | using IdentityModel; 6 | using IdentityServer4.Services.InMemory; 7 | 8 | static class Users 9 | { 10 | public static List Get() 11 | => 12 | new List 13 | { 14 | new InMemoryUser{Subject = "818727", Username = "alice", Password = "alice", 15 | Claims = new[] 16 | { 17 | new Claim(JwtClaimTypes.Name, "Alice Smith"), 18 | new Claim(JwtClaimTypes.GivenName, "Alice"), 19 | new Claim(JwtClaimTypes.FamilyName, "Smith"), 20 | new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"), 21 | new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 22 | new Claim(JwtClaimTypes.Role, "User"), 23 | new Claim(JwtClaimTypes.Id, "1", ClaimValueTypes.Integer64) 24 | } 25 | }, 26 | new InMemoryUser{Subject = "88421113", Username = "bob", Password = "bob", 27 | Claims = new[] 28 | { 29 | new Claim(JwtClaimTypes.Name, "Bob Smith"), 30 | new Claim(JwtClaimTypes.GivenName, "Bob"), 31 | new Claim(JwtClaimTypes.FamilyName, "Smith"), 32 | new Claim(JwtClaimTypes.Email, "BobSmith@email.com"), 33 | new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 34 | new Claim(JwtClaimTypes.Role, "User"), 35 | new Claim(JwtClaimTypes.Id, "2", ClaimValueTypes.Integer64) 36 | } 37 | } 38 | }; 39 | } 40 | } -------------------------------------------------------------------------------- /src/Chapter12/Login/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/Chapter12/Login/Login.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0.25123 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | f416d0b1-71ee-400e-91e2-fc2b047da208 10 | Login 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter12/Login/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Login 2 | { 3 | using System.IO; 4 | using Microsoft.AspNetCore.Hosting; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var host = new WebHostBuilder() 11 | .UseKestrel() 12 | .UseContentRoot(Directory.GetCurrentDirectory()) 13 | .UseIISIntegration() 14 | .UseStartup() 15 | .UseUrls("http://localhost:5001") 16 | .Build(); 17 | 18 | host.Run(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Chapter12/Login/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "IdentityServer": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter12/Login/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace Login 2 | { 3 | using System.IO; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using System.Security.Cryptography.X509Certificates; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Extensions.DependencyInjection; 10 | 11 | using Configuration; 12 | 13 | public class Startup 14 | { 15 | private readonly IHostingEnvironment environment; 16 | 17 | public Startup(IHostingEnvironment env) 18 | { 19 | this.environment = env; 20 | } 21 | 22 | public void ConfigureServices(IServiceCollection services) 23 | { 24 | var cert = new X509Certificate2(Path.Combine(this.environment.ContentRootPath, "idsrv3test.pfx"), "idsrv3test"); 25 | 26 | services.AddSingleton(); 27 | var builder = services 28 | .AddIdentityServer() 29 | .SetSigningCredential(cert) 30 | .AddInMemoryClients(Clients.Get()) 31 | .AddInMemoryScopes(Scopes.Get()) 32 | .AddInMemoryUsers(Users.Get()); 33 | 34 | services.AddMvc(); 35 | } 36 | 37 | public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) 38 | { 39 | loggerFactory.AddConsole(LogLevel.Trace); 40 | loggerFactory.AddDebug(LogLevel.Trace); 41 | 42 | app.UseCookieAuthentication(new CookieAuthenticationOptions 43 | { 44 | AuthenticationScheme = "Temp", 45 | AutomaticAuthenticate = false, 46 | AutomaticChallenge = false 47 | }); 48 | 49 | app.UseIdentityServer(); 50 | 51 | app.UseStaticFiles(); 52 | app.UseMvcWithDefaultRoute(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Chapter12/Login/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Microsoft.AspNetCore.StaticFiles": "1.0.0", 10 | "Microsoft.AspNetCore.Mvc": "1.0.0", 11 | "Microsoft.Extensions.Logging.Console": "1.0.0", 12 | "Microsoft.Extensions.Logging.Debug": "1.0.0", 13 | "IdentityServer4": "1.0.0-beta5", 14 | "SeriLog": "2.0.0-rc-600" 15 | }, 16 | 17 | "tools": { 18 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 19 | }, 20 | 21 | "frameworks": { 22 | "netcoreapp1.0": { 23 | "imports": [ 24 | "dotnet5.6", 25 | "portable-net45+win8" 26 | ] 27 | } 28 | }, 29 | 30 | "buildOptions": { 31 | "emitEntryPoint": true, 32 | "preserveCompilationContext": true 33 | }, 34 | 35 | "runtimeOptions": { 36 | "gcServer": true 37 | }, 38 | 39 | "publishOptions": { 40 | "include": [ 41 | "wwwroot", 42 | "web.config" 43 | ] 44 | }, 45 | 46 | "scripts": { 47 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 48 | }, 49 | 50 | "tooling": { 51 | "defaultNamespace": "IdentityServer" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Chapter12/Login/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter12/ProductCatalog/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:latest 2 | 3 | COPY . /app 4 | 5 | WORKDIR /app 6 | 7 | RUN ["dotnet", "restore"] 8 | 9 | RUN ["dotnet", "build"] 10 | 11 | EXPOSE 5000/tcp 12 | 13 | ENTRYPOINT ["dotnet", "run", "--server.urls", "http://0.0.0.0:5000"] 14 | -------------------------------------------------------------------------------- /src/Chapter12/ProductCatalog/Program.cs: -------------------------------------------------------------------------------- 1 | namespace ProductCatalog 2 | { 3 | using System.IO; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Configuration; 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var config = new ConfigurationBuilder() 11 | .AddEnvironmentVariables(prefix: "ASPNETCORE_") 12 | .Build(); 13 | 14 | var host = new WebHostBuilder() 15 | .UseConfiguration(config) 16 | .UseKestrel() 17 | .UseUrls("http://localhost:5100") 18 | .UseContentRoot(Directory.GetCurrentDirectory()) 19 | .UseIISIntegration() 20 | .UseStartup() 21 | .Build(); 22 | host.Run(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Chapter12/ProductCatalog/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ProductCatalog": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "http://localhost:5000", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Chapter12/ProductCatalog/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace ProductCatalog 2 | { 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Logging; 6 | using Nancy.Owin; 7 | 8 | public class Startup 9 | { 10 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 11 | { 12 | app.UseOwin().UseNancy(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Chapter12/ProductCatalog/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Microsoft.AspNetCore.Owin": "1.0.0", 10 | "Nancy": "2.0.0-barneyrubble" 11 | }, 12 | 13 | "tools": { 14 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 15 | }, 16 | 17 | "frameworks": { 18 | "netcoreapp1.0": { 19 | "imports": [ 20 | "dotnet5.6", 21 | "portable-net45+win8" 22 | ] 23 | } 24 | }, 25 | 26 | "buildOptions": { 27 | "emitEntryPoint": true, 28 | "preserveCompilationContext": true 29 | }, 30 | 31 | "runtimeOptions": { 32 | "configProperties": { 33 | "System.GC.Server": true 34 | } 35 | }, 36 | 37 | "publishOptions": { 38 | "include": [ 39 | "wwwroot", 40 | "web.config" 41 | ] 42 | }, 43 | 44 | "scripts": { 45 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 46 | }, 47 | 48 | "tooling": { 49 | "defaultNamespace": "ProductCatalog" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Chapter12/ProductCatalog/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (console)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "stopAtEntry": false 13 | }, 14 | { 15 | "name": ".NET Core Launch (web)", 16 | "type": "coreclr", 17 | "request": "launch", 18 | "preLaunchTask": "build", 19 | "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", 20 | "args": [], 21 | "cwd": "${workspaceRoot}", 22 | "stopAtEntry": false, 23 | "launchBrowser": { 24 | "enabled": true, 25 | "args": "${auto-detect-url}", 26 | "windows": { 27 | "command": "cmd.exe", 28 | "args": "/C start ${auto-detect-url}" 29 | }, 30 | "osx": { 31 | "command": "open" 32 | }, 33 | "linux": { 34 | "command": "xdg-open" 35 | } 36 | } 37 | }, 38 | { 39 | "name": ".NET Core Attach", 40 | "type": "coreclr", 41 | "request": "attach", 42 | "processName": "" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [], 10 | "isBuildCommand": true, 11 | "problemMatcher": "$msCompile" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/EventFeed/Event.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using System; 4 | 5 | public struct Event 6 | { 7 | public long SequenceNumber { get; } 8 | public DateTimeOffset OccuredAt { get; } 9 | public string Name { get; } 10 | public object Content { get; } 11 | 12 | public Event( 13 | long sequenceNumber, 14 | DateTimeOffset occuredAt, 15 | string name, 16 | object content) 17 | { 18 | this.SequenceNumber = sequenceNumber; 19 | this.OccuredAt = occuredAt; 20 | this.Name = name; 21 | this.Content = content; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/EventFeed/EventsFeedModule.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using Nancy; 4 | 5 | public class EventsFeedModule : NancyModule 6 | { 7 | public EventsFeedModule(IEventStore eventStore) : base("/events") 8 | { 9 | Get("/", _ => 10 | { 11 | long firstEventSequenceNumber, lastEventSequenceNumber; 12 | if (!long.TryParse(this.Request.Query.start.Value, 13 | out firstEventSequenceNumber)) 14 | firstEventSequenceNumber = 0; 15 | if (!long.TryParse(this.Request.Query.end.Value, 16 | out lastEventSequenceNumber)) 17 | lastEventSequenceNumber = long.MaxValue; 18 | 19 | return 20 | eventStore.GetEvents( 21 | firstEventSequenceNumber, 22 | lastEventSequenceNumber); 23 | }); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/EventFeed/IEventStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.EventFeed 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | public interface IEventStore 7 | { 8 | Task> GetEvents(long firstEventSequenceNumber, 9 | long lastEventSequenceNumber); 10 | Task Raise(string eventName, object content); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/ICache.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart 2 | { 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | 7 | public interface ICache 8 | { 9 | void Add(string key, object value, TimeSpan ttl); 10 | object Get(string productsResource); 11 | } 12 | 13 | public class Cache : ICache 14 | { 15 | private static IDictionary> cache = new ConcurrentDictionary>(); 16 | 17 | public void Add(string key, object value, TimeSpan ttl) 18 | { 19 | cache[key] = Tuple.Create(DateTimeOffset.UtcNow.Add(ttl), value); 20 | } 21 | 22 | public object Get(string productsResource) 23 | { 24 | Tuple value; 25 | if (cache.TryGetValue(productsResource, out value) && value.Item1 > DateTimeOffset.UtcNow) 26 | return value; 27 | cache.Remove(productsResource); 28 | return null; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/IProductCatalogueClient.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart 2 | { 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using ShoppingCart; 6 | 7 | public interface IProductCatalogueClient 8 | { 9 | Task> 10 | GetShoppingCartItems(int[] productCatalogueIds); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace ShoppingCart 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var host = new WebHostBuilder() 11 | .UseKestrel() 12 | .UseUrls("http://localhost:5200") 13 | .UseContentRoot(Directory.GetCurrentDirectory()) 14 | .UseIISIntegration() 15 | .UseStartup() 16 | .Build(); 17 | 18 | host.Run(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "ShoppingCart": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/ShoppingCart.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10e4f601-c16b-4936-a7b4-d32d799318d1 10 | ShoppingCart 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/ShoppingCart/IShoppingCartStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | using System.Threading.Tasks; 4 | 5 | public interface IShoppingCartStore 6 | { 7 | Task Get(int userId); 8 | Task Save(ShoppingCart shoppingCart); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/ShoppingCart/ShoppingCartModule.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | using EventFeed; 4 | using Nancy; 5 | using Nancy.ModelBinding; 6 | 7 | public class ShoppingCartModule : NancyModule 8 | { 9 | public ShoppingCartModule( 10 | IShoppingCartStore shoppingCartStore, 11 | IProductCatalogueClient productCatalogue, 12 | IEventStore eventStore) 13 | : base("/shoppingcart") 14 | { 15 | Get("/{userid:int}", parameters => 16 | { 17 | var userId = (int) parameters.userid; 18 | return shoppingCartStore.Get(userId); 19 | }); 20 | 21 | Post("/{userid:int}/items", async parameters => 22 | { 23 | var productCatalogueIds = this.Bind(); 24 | var userId = (int) parameters.userid; 25 | 26 | var shoppingCart = await shoppingCartStore.Get(userId).ConfigureAwait(false); 27 | var shoppingCartItems = await productCatalogue.GetShoppingCartItems(productCatalogueIds).ConfigureAwait(false); 28 | shoppingCart.AddItems(shoppingCartItems, eventStore); 29 | await shoppingCartStore.Save(shoppingCart); 30 | 31 | return shoppingCart; 32 | }); 33 | 34 | Delete("/{userid:int}/items", async parameters => 35 | { 36 | var productCatalogueIds = this.Bind(); 37 | var userId = (int)parameters.userid; 38 | 39 | var shoppingCart = await shoppingCartStore.Get(userId).ConfigureAwait(false); 40 | shoppingCart.RemoveItems(productCatalogueIds, eventStore); 41 | await shoppingCartStore.Save(shoppingCart); 42 | 43 | return shoppingCart; 44 | }); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/ShoppingCart/ShoppingCartStore.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart.ShoppingCart 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data.SqlClient; 6 | using System.Threading.Tasks; 7 | using Dapper; 8 | 9 | public class ShoppingCartStore : IShoppingCartStore 10 | { 11 | private string connectionString = 12 | @"Data Source=.\SQLEXPRESS;Initial Catalog=ShoppingCart; 13 | Integrated Security=True"; 14 | 15 | private const string readItemsSql = 16 | @"select * from ShoppingCart, ShoppingCartItems 17 | where ShoppingCartItems.ShoppingCartId = ID 18 | and ShoppingCart.UserId=@UserId"; 19 | 20 | public async Task Get(int userId) 21 | { 22 | using (var conn = new SqlConnection(connectionString)) 23 | { 24 | var items = await 25 | conn.QueryAsync( 26 | readItemsSql, 27 | new { UserId = userId }); 28 | return new ShoppingCart(userId, items); 29 | } 30 | } 31 | 32 | private const string deleteAllForShoppingCartSql= 33 | @"delete item from ShoppingCartItems item 34 | inner join ShoppingCart cart on item.ShoppingCartId = cart.ID 35 | and cart.UserId=@UserId"; 36 | 37 | private const string addAllForShoppingCartSql= 38 | @"insert into ShoppingCartItems 39 | (ShoppingCartId, ProductCatalogId, ProductName, 40 | ProductDescription, Amount, Currency) 41 | values 42 | (@ShoppingCartId, @ProductCatalogId, @ProductName,v 43 | @ProductDescription, @Amount, @Currency)"; 44 | 45 | public async Task Save(ShoppingCart shoppingCart) 46 | { 47 | using (var conn = new SqlConnection(connectionString)) 48 | using (var tx = conn.BeginTransaction()) 49 | { 50 | await conn.ExecuteAsync( 51 | deleteAllForShoppingCartSql, 52 | new { UserId = shoppingCart.UserId }, 53 | tx).ConfigureAwait(false); 54 | await conn.ExecuteAsync( 55 | addAllForShoppingCartSql, 56 | shoppingCart.Items, 57 | tx).ConfigureAwait(false); 58 | } 59 | } 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace ShoppingCart 2 | { 3 | using Microsoft.AspNetCore.Builder; 4 | using Nancy; 5 | using Nancy.Configuration; 6 | using Nancy.Owin; 7 | 8 | public class Startup 9 | { 10 | public void Configure(IApplicationBuilder app) 11 | { 12 | app.UseOwin().UseNancy(opt => opt.Bootstrapper = new TracingBootstrapper()); 13 | } 14 | } 15 | 16 | public class TracingBootstrapper : Nancy.DefaultNancyBootstrapper 17 | { 18 | public override void Configure(INancyEnvironment env) 19 | { 20 | env.Tracing(enabled: true, displayErrorTraces: true); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/database-scripts/create-shopping-cart-db.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE ShoppingCart 2 | GO 3 | 4 | USE [ShoppingCart] 5 | GO 6 | 7 | CREATE TABLE [dbo].[ShoppingCart]( 8 | [ID] int IDENTITY(1,1) PRIMARY KEY, 9 | [UserId] [bigint] NOT NULL, 10 | CONSTRAINT ShoppingCartUnique UNIQUE([ID], [UserID]) 11 | ) 12 | GO 13 | 14 | CREATE INDEX ShoppingCart_UserId 15 | ON [dbo].[ShoppingCart] (UserId) 16 | GO 17 | 18 | CREATE TABLE [dbo].[ShoppingCartItems]( 19 | [ID] int IDENTITY(1,1) PRIMARY KEY, 20 | [ShoppingCartId] [int] NOT NULL, 21 | [ProductCatalogId] [bigint] NOT NULL, 22 | [ProductName] [nvarchar](100) NOT NULL, 23 | [ProductDescription] [nvarchar](500) NULL, 24 | [Amount] [int] NOT NULL, 25 | [Currency] [nvarchar](5) NOT NULL 26 | ) 27 | 28 | GO 29 | 30 | ALTER TABLE [dbo].[ShoppingCartItems] WITH CHECK ADD CONSTRAINT [FK_ShoppingCart] FOREIGN KEY([ShoppingCartId]) 31 | REFERENCES [dbo].[ShoppingCart] ([Id]) 32 | GO 33 | 34 | ALTER TABLE [dbo].[ShoppingCartItems] CHECK CONSTRAINT [FK_ShoppingCart] 35 | GO 36 | 37 | CREATE INDEX ShoppingCartItems_ShoppingCartId 38 | ON [dbo].[ShoppingCartItems] (ShoppingCartId) 39 | GO 40 | 41 | CREATE TABLE [dbo].[EventStore]( 42 | [ID] int IDENTITY(1,1) PRIMARY KEY, 43 | [Name] [nvarchar](100) NOT NULL, 44 | [OccuredAt] [datetimeoffset] NOT NULL, 45 | [Content][nvarchar](max) NOT NULL 46 | ) 47 | GO 48 | 49 | -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 4 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 5 | "Microsoft.AspNetCore.Owin": "1.0.0", 6 | "Nancy": "2.0.0-barneyrubble", 7 | "Polly": "4.2.1", 8 | "Dapper": "1.50.0-rc2a" 9 | }, 10 | "runtimes": { 11 | "win10-x64": "" 12 | }, 13 | 14 | "tools": { 15 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 16 | }, 17 | 18 | "frameworks": { 19 | "netcoreapp1.0": { 20 | "imports": [ 21 | "dotnet5.6", 22 | "portable-net45+win8" 23 | ], 24 | "dependencies": { 25 | "System.Net.Http": "4.0.1" 26 | } 27 | }, 28 | "net461": { 29 | "buildOptions": {"define": ["net461"]}, 30 | "frameworkAssemblies": { 31 | "System.Net.Http": "4.0.0.0", 32 | "System.Runtime": "4.0.20.0" 33 | }, 34 | "dependencies": { 35 | "EventStore.Client": "3.3.1", 36 | "Microsoft.CSharp": "4.0.0" 37 | } 38 | } 39 | }, 40 | 41 | "buildOptions": { 42 | "emitEntryPoint": true, 43 | "preserveCompilationContext": true 44 | }, 45 | 46 | "runtimeOptions": { 47 | "gcServer": true 48 | }, 49 | 50 | "publishOptions": { 51 | "include": [ 52 | "wwwroot", 53 | "web.config" 54 | ] 55 | }, 56 | 57 | "scripts": { 58 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 59 | }, 60 | 61 | "tooling": { 62 | "defaultNamespace": "HelloMicroservices" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Chapter12/ShoppingCart/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Chapter12/start-app.ps1: -------------------------------------------------------------------------------- 1 | start-process dotnet -ArgumentList run -WorkingDirectory ./Login 2 | start-process dotnet -ArgumentList run -WorkingDirectory ./ProductCatalog 3 | start-process dotnet -ArgumentList run -WorkingDirectory ./ShoppingCart 4 | start-process dotnet -ArgumentList run -WorkingDirectory ./ApiGateway -------------------------------------------------------------------------------- /src/chapter03/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horsdal/microservices-in-dotnetcore/4265349524e81e99bab984c921a388f424516fea/src/chapter03/readme.md -------------------------------------------------------------------------------- /src/chapter08/OwinMiddlewareTests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | 4 | "buildOptions": { 5 | "preserveCompilationContext": true 6 | }, 7 | 8 | "dependencies": { 9 | "dotnet-test-xunit": "2.2.0-preview2-build1029", 10 | "Microsoft.NETCore.App": { 11 | "version": "1.0.0", 12 | "type": "platform" 13 | }, 14 | "xunit": "2.1.0", 15 | "System.Security.Claims": "4.0.1", 16 | "System.Globalization": "4.0.11" 17 | }, 18 | 19 | "frameworks": { 20 | "netcoreapp1.0": { 21 | "imports": [ 22 | "dotnet5.6", 23 | "portable-net45+win8" 24 | ], 25 | "dependencies": { 26 | } 27 | } 28 | }, 29 | 30 | "testRunner": "xunit", 31 | 32 | "tooling": { 33 | "defaultNamespace": "OwinMiddlewareTests" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/chapter10/.idea.ch10/riderModule.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/chapter10/ApiGatewayMock/ApiGatewayMock.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | fe109a02-bf1b-46fc-87c8-2a68781ce8a4 10 | ApiGatewayMock 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/chapter10/ApiGatewayMock/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-*", 3 | "buildOptions": { 4 | "emitEntryPoint": true 5 | }, 6 | 7 | "dependencies": { 8 | "Microsoft.NETCore.App": { 9 | "type": "platform", 10 | "version": "1.0.0" 11 | }, 12 | "Newtonsoft.Json": "8.0.4-beta1", 13 | "Polly": "4.2.1", 14 | "IdentityModel": "2.0.0-beta3", 15 | "System.Net.Http": "4.1.0" 16 | }, 17 | 18 | "frameworks": { 19 | "netcoreapp1.0": { 20 | "imports": "dnxcore50" 21 | } 22 | }, 23 | 24 | "tooling": { 25 | "defaultNamespace": "ApiGatewayMock" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/chapter10/Login/Configuration/Clients.cs: -------------------------------------------------------------------------------- 1 | namespace Login.Configuration 2 | { 3 | using System.Collections.Generic; 4 | using IdentityServer4.Models; 5 | 6 | public class Clients 7 | { 8 | public static IEnumerable Get() => 9 | new List 10 | { 11 | new Client 12 | { 13 | ClientName = "API Gateway", 14 | ClientId = "api_gateway", 15 | ClientSecrets = new List 16 | { 17 | new Secret("secret".Sha256()) 18 | }, 19 | AllowedScopes = new List 20 | { 21 | "loyalty_program_write", 22 | }, 23 | AllowedGrantTypes = GrantTypes.ClientCredentials 24 | }, 25 | 26 | new Client 27 | { 28 | ClientName = "Web Client", 29 | ClientId = "web", 30 | 31 | RedirectUris = new List 32 | { 33 | "http://localhost:5003/signin-oidc", 34 | }, 35 | PostLogoutRedirectUris = new List 36 | { 37 | "http://localhost:5003/", 38 | }, 39 | 40 | AllowedScopes = new List 41 | { 42 | "openid", 43 | "email", 44 | "profile", 45 | } 46 | } 47 | }; 48 | } 49 | } -------------------------------------------------------------------------------- /src/chapter10/Login/Configuration/Scopes.cs: -------------------------------------------------------------------------------- 1 | namespace Login.Configuration 2 | { 3 | using System.Collections.Generic; 4 | using IdentityServer4.Models; 5 | 6 | public class Scopes 7 | { 8 | public static IEnumerable Get() => 9 | new[] 10 | { 11 | // standard OpenID Connect scopes 12 | StandardScopes.OpenId, 13 | StandardScopes.ProfileAlwaysInclude, 14 | StandardScopes.EmailAlwaysInclude, 15 | new Scope 16 | { 17 | Name = "loyalty_program_write", 18 | DisplayName = "Loyalty Program write access", 19 | Type = ScopeType.Resource, 20 | } 21 | }; 22 | } 23 | } -------------------------------------------------------------------------------- /src/chapter10/Login/Configuration/Users.cs: -------------------------------------------------------------------------------- 1 | namespace Login.Configuration 2 | { 3 | using System.Collections.Generic; 4 | using System.Security.Claims; 5 | using IdentityModel; 6 | using IdentityServer4.Services.InMemory; 7 | 8 | static class Users 9 | { 10 | public static List Get() 11 | => 12 | new List 13 | { 14 | new InMemoryUser{Subject = "818727", Username = "alice", Password = "alice", 15 | Claims = new[] 16 | { 17 | new Claim(JwtClaimTypes.Name, "Alice Smith"), 18 | new Claim(JwtClaimTypes.GivenName, "Alice"), 19 | new Claim(JwtClaimTypes.FamilyName, "Smith"), 20 | new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"), 21 | new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 22 | new Claim(JwtClaimTypes.Role, "User"), 23 | new Claim(JwtClaimTypes.Id, "1", ClaimValueTypes.Integer64) 24 | } 25 | }, 26 | new InMemoryUser{Subject = "88421113", Username = "bob", Password = "bob", 27 | Claims = new[] 28 | { 29 | new Claim(JwtClaimTypes.Name, "Bob Smith"), 30 | new Claim(JwtClaimTypes.GivenName, "Bob"), 31 | new Claim(JwtClaimTypes.FamilyName, "Smith"), 32 | new Claim(JwtClaimTypes.Email, "BobSmith@email.com"), 33 | new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 34 | new Claim(JwtClaimTypes.Role, "User"), 35 | new Claim(JwtClaimTypes.Id, "2", ClaimValueTypes.Integer64) 36 | } 37 | } 38 | }; 39 | } 40 | } -------------------------------------------------------------------------------- /src/chapter10/Login/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/chapter10/Login/Login.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0.25123 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | f416d0b1-71ee-400e-91e2-fc2b047da208 10 | Login 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/chapter10/Login/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Login 2 | { 3 | using System.IO; 4 | using Microsoft.AspNetCore.Hosting; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | var host = new WebHostBuilder() 11 | .UseKestrel() 12 | .UseContentRoot(Directory.GetCurrentDirectory()) 13 | .UseIISIntegration() 14 | .UseStartup() 15 | .UseUrls("http://localhost:5001") 16 | .Build(); 17 | 18 | host.Run(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/chapter10/Login/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "IdentityServer": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/chapter10/Login/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace Login 2 | { 3 | using System.IO; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using System.Security.Cryptography.X509Certificates; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Extensions.DependencyInjection; 10 | 11 | using Configuration; 12 | 13 | public class Startup 14 | { 15 | private readonly IHostingEnvironment environment; 16 | 17 | public Startup(IHostingEnvironment env) 18 | { 19 | this.environment = env; 20 | } 21 | 22 | public void ConfigureServices(IServiceCollection services) 23 | { 24 | var cert = new X509Certificate2(Path.Combine(this.environment.ContentRootPath, "idsrv3test.pfx"), "idsrv3test"); 25 | 26 | services.AddSingleton(); 27 | var builder = services.AddIdentityServer().SetSigningCredential(cert); 28 | 29 | builder.AddInMemoryClients(Clients.Get()); 30 | builder.AddInMemoryScopes(Scopes.Get()); 31 | builder.AddInMemoryUsers(Users.Get()); 32 | 33 | services.AddMvc(); 34 | } 35 | 36 | public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) 37 | { 38 | loggerFactory.AddConsole(LogLevel.Trace); 39 | loggerFactory.AddDebug(LogLevel.Trace); 40 | 41 | app.UseCookieAuthentication(new CookieAuthenticationOptions 42 | { 43 | AuthenticationScheme = "Temp", 44 | AutomaticAuthenticate = false, 45 | AutomaticChallenge = false 46 | }); 47 | 48 | app.UseIdentityServer(); 49 | 50 | app.UseStaticFiles(); 51 | app.UseMvcWithDefaultRoute(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/chapter10/Login/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Microsoft.AspNetCore.StaticFiles": "1.0.0", 10 | "Microsoft.AspNetCore.Mvc": "1.0.0", 11 | "Microsoft.Extensions.Logging.Console": "1.0.0", 12 | "Microsoft.Extensions.Logging.Debug": "1.0.0", 13 | "IdentityServer4": "1.0.0-beta5", 14 | "SeriLog": "2.0.0-rc-600" 15 | }, 16 | 17 | "tools": { 18 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 19 | }, 20 | 21 | "frameworks": { 22 | "netcoreapp1.0": { 23 | "imports": [ 24 | "dotnet5.6", 25 | "portable-net45+win8" 26 | ] 27 | } 28 | }, 29 | 30 | "buildOptions": { 31 | "emitEntryPoint": true, 32 | "preserveCompilationContext": true 33 | }, 34 | 35 | "runtimeOptions": { 36 | "gcServer": true 37 | }, 38 | 39 | "publishOptions": { 40 | "include": [ 41 | "wwwroot", 42 | "web.config" 43 | ] 44 | }, 45 | 46 | "scripts": { 47 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 48 | }, 49 | 50 | "tooling": { 51 | "defaultNamespace": "IdentityServer" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/chapter10/Login/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/chapter10/LoyaltyProgram/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | namespace LoyaltyProgram 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Security.Claims; 6 | using Nancy; 7 | using Nancy.Bootstrapper; 8 | using Nancy.TinyIoc; 9 | using Nancy.Owin; 10 | 11 | public class Bootstrapper : DefaultNancyBootstrapper 12 | { 13 | protected override Func InternalConfiguration 14 | => NancyInternalConfiguration.WithOverrides(builder => builder.StatusCodeHandlers.Clear()); 15 | 16 | protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) 17 | { 18 | pipelines.OnError += (ctx, ex) => 19 | { 20 | // write to central log store 21 | return null; 22 | }; 23 | } 24 | } 25 | 26 | public class SetUser : IRequestStartup 27 | { 28 | public void Initialize(IPipelines pipelines, NancyContext context) => 29 | context.CurrentUser = context.GetOwinEnvironment()["pos-end-user"] as ClaimsPrincipal; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/chapter10/LoyaltyProgram/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnet:1.0.0-rc1-update1 2 | 3 | RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list 4 | RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* 5 | 6 | COPY . /app 7 | WORKDIR /app 8 | RUN ["dnu", "restore"] 9 | 10 | EXPOSE 5000/tcp 11 | ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] 12 | -------------------------------------------------------------------------------- /src/chapter10/LoyaltyProgram/LoyaltyProgram.xproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 4bed3c45-e8c3-4345-a08f-249a39a256eb 10 | LoyaltyProgram 11 | .\obj 12 | .\bin\ 13 | 14 | 15 | 16 | 2.0 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/chapter10/LoyaltyProgram/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace LoyaltyProgram 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | var host = new WebHostBuilder() 15 | .UseKestrel() 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseIISIntegration() 18 | .UseStartup() 19 | .Build(); 20 | 21 | host.Run(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/chapter10/LoyaltyProgram/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "LoyaltyProgram": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/chapter10/LoyaltyProgram/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.App": { 4 | "version": "1.0.0", 5 | "type": "platform" 6 | }, 7 | "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", 8 | "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", 9 | "Microsoft.AspNetCore.Owin": "1.0.0", 10 | "Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0", 11 | "System.Text.Encodings.Web": "4.0.0", 12 | "Nancy": "2.0.0-barneyrubble", 13 | "YamlDotNet": "3.8.0-pre233" 14 | }, 15 | 16 | "tools": { 17 | "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" 18 | }, 19 | 20 | "frameworks": { 21 | "netcoreapp1.0": { 22 | "imports": [ 23 | "dotnet5.6", 24 | "portable-net45+win8" 25 | ] 26 | } 27 | }, 28 | 29 | "buildOptions": { 30 | "emitEntryPoint": true, 31 | "preserveCompilationContext": true 32 | }, 33 | 34 | "runtimeOptions": { 35 | "gcServer": true 36 | }, 37 | 38 | "publishOptions": { 39 | "include": [ 40 | "wwwroot", 41 | "web.config" 42 | ] 43 | }, 44 | 45 | "scripts": { 46 | "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] 47 | }, 48 | 49 | "tooling": { 50 | "defaultNamespace": "LoyaltyProgram" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/chapter10/LoyaltyProgram/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/chapter10/ch10.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25123.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoyaltyProgram", "LoyaltyProgram\LoyaltyProgram.xproj", "{4BED3C45-E8C3-4345-A08F-249A39A256EB}" 7 | EndProject 8 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApiGatewayMock", "ApiGatewayMock\ApiGatewayMock.xproj", "{FE109A02-BF1B-46FC-87C8-2A68781CE8A4}" 9 | EndProject 10 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Login", "Login\Login.xproj", "{F416D0B1-71EE-400E-91E2-FC2B047DA208}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {F416D0B1-71EE-400E-91E2-FC2B047DA208}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {F416D0B1-71EE-400E-91E2-FC2B047DA208}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {F416D0B1-71EE-400E-91E2-FC2B047DA208}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {F416D0B1-71EE-400E-91E2-FC2B047DA208}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /src/chapter10/start-app.ps1: -------------------------------------------------------------------------------- 1 | start-process dotnet -ArgumentList run -WorkingDirectory ./IdentityServer 2 | start-process dotnet -ArgumentList run -WorkingDirectory ./LoyaltyProgram 3 | start-process dotnet -ArgumentList run -WorkingDirectory ./ApiGatewayMock 4 | -------------------------------------------------------------------------------- /src/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ "Chapter11" ], 3 | "sdk": { 4 | "version": "1.0.0-preview2-003121" 5 | } 6 | } 7 | --------------------------------------------------------------------------------