Repository: horsdal/microservices-in-dotnetcore Branch: master Commit: 4265349524e8 Files: 301 Total size: 1.1 MB Directory structure: gitextract_c0ak1tpq/ ├── .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/ │ │ │ ├── IShoppingCartStore.cs │ │ │ ├── ShoppingCart.cs │ │ │ ├── ShoppingCartModule.cs │ │ │ └── ShoppingCartStore.cs │ │ ├── ShoppingCart.xproj │ │ ├── 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/ │ │ ├── IShoppingCartStore.cs │ │ ├── ShoppingCart.cs │ │ ├── ShoppingCartModule.cs │ │ └── ShoppingCartStore.cs │ ├── ShoppingCart.xproj │ ├── 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/ │ │ ├── IShoppingCartStore.cs │ │ ├── ShoppingCart.cs │ │ ├── ShoppingCartModule.cs │ │ └── ShoppingCartStore.cs │ ├── ShoppingCart.xproj │ ├── 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/ │ │ │ ├── IShoppingCartStore.cs │ │ │ ├── ShoppingCart.cs │ │ │ ├── ShoppingCartModule.cs │ │ │ └── ShoppingCartStore.cs │ │ ├── ShoppingCart.xproj │ │ ├── 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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2016 Christian Horsdal Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Code samples from Microservices in .NET Core 1st edition 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 The code samples from my microservices book - https://manning.com/books/microservices-in-net-core [![](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) ## About the book 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. ## Branches 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: * `master`: The code as it appears in the book * `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. ================================================ FILE: src/Chapter01/HelloMicroservices/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter01/HelloMicroservices/.vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "name": ".NET Core Launch (console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/HelloMicroservices.dll", "args": [], "cwd": "${workspaceRoot}", "stopAtEntry": false }, { "name": ".NET Core Launch (web)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/HelloMicroservices.dll", "args": [], "cwd": "${workspaceRoot}", "stopAtEntry": false, "launchBrowser": { "enabled": true, "args": "${auto-detect-url}", "windows": { "command": "cmd.exe", "args": "/C start ${auto-detect-url}" }, "osx": { "command": "open" }, "linux": { "command": "xdg-open" } } }, { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", "processName": "" } ] } ================================================ FILE: src/Chapter01/HelloMicroservices/.vscode/tasks.json ================================================ { "version": "0.1.0", "command": "dotnet", "isShellCommand": true, "args": [], "tasks": [ { "taskName": "build", "args": [], "isBuildCommand": true, "problemMatcher": "$msCompile" } ] } ================================================ FILE: src/Chapter01/HelloMicroservices/CurrentDateTimeModule.cs ================================================ namespace HelloMicroservices { using System; using Nancy; public class CurrentDateTimeModule : NancyModule { public CurrentDateTimeModule() { Get("/", _ => DateTime.UtcNow); } } } ================================================ FILE: src/Chapter01/HelloMicroservices/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter01/HelloMicroservices/HelloMicroservices.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 74316f9a-d6b2-451a-b709-b3dc753d9a8c HelloMicroservices .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter01/HelloMicroservices/Program.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; namespace HelloMicroservices { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter01/HelloMicroservices/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "HelloMicroservices": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter01/HelloMicroservices/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter01/HelloMicroservices/Startup.cs ================================================ namespace HelloMicroservices { using Microsoft.AspNetCore.Builder; using Nancy.Owin; public class Startup { public void Configure(IApplicationBuilder app) { app.UseOwin().UseNancy(); } } } ================================================ FILE: src/Chapter01/HelloMicroservices/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Owin": "1.0.0", "Nancy": "2.0.0-barneyrubble" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "HelloMicroservices" } } ================================================ FILE: src/Chapter01/HelloMicroservices/web.config ================================================ ================================================ FILE: src/Chapter01/ch01.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "HelloMicroservices", "HelloMicroservices\HelloMicroservices.xproj", "{74316F9A-D6B2-451A-B709-B3DC753D9A8C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {74316F9A-D6B2-451A-B709-B3DC753D9A8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {74316F9A-D6B2-451A-B709-B3DC753D9A8C}.Debug|Any CPU.Build.0 = Debug|Any CPU {74316F9A-D6B2-451A-B709-B3DC753D9A8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {74316F9A-D6B2-451A-B709-B3DC753D9A8C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: src/Chapter02/ShoppingCart/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter02/ShoppingCart/.vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "name": ".NET Core Launch (console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", "args": [], "cwd": "${workspaceRoot}", "stopAtEntry": false }, { "name": ".NET Core Launch (web)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", "args": [], "cwd": "${workspaceRoot}", "stopAtEntry": false, "launchBrowser": { "enabled": true, "args": "${auto-detect-url}", "windows": { "command": "cmd.exe", "args": "/C start ${auto-detect-url}" }, "osx": { "command": "open" }, "linux": { "command": "xdg-open" } } }, { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", "processName": "" } ] } ================================================ FILE: src/Chapter02/ShoppingCart/.vscode/tasks.json ================================================ { "version": "0.1.0", "command": "dotnet", "isShellCommand": true, "args": [], "tasks": [ { "taskName": "build", "args": [], "isBuildCommand": true, "problemMatcher": "$msCompile" } ] } ================================================ FILE: src/Chapter02/ShoppingCart/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter02/ShoppingCart/EventFeed/Event.cs ================================================ namespace ShoppingCart.EventFeed { using System; public struct Event { public long SequenceNumber { get; } public DateTimeOffset OccuredAt { get; } public string Name { get; } public object Content { get; } public Event( long sequenceNumber, DateTimeOffset occuredAt, string name, object content) { this.SequenceNumber = sequenceNumber; this.OccuredAt = occuredAt; this.Name = name; this.Content = content; } } } ================================================ FILE: src/Chapter02/ShoppingCart/EventFeed/EventStore.cs ================================================ namespace ShoppingCart.EventFeed { using System; using System.Collections.Generic; using System.Linq; using System.Threading; using ShoppingCart; public class EventStore : IEventStore { private static long currentSequenceNumber = 0; private static readonly IList database = new List(); public IEnumerable GetEvents( long firstEventSequenceNumber, long lastEventSequenceNumber) => database .Where(e => e.SequenceNumber >= firstEventSequenceNumber && e.SequenceNumber <= lastEventSequenceNumber) .OrderBy(e => e.SequenceNumber); public void Raise(string eventName, object content) { var seqNumber = Interlocked.Increment(ref currentSequenceNumber); database.Add( new Event( seqNumber, DateTimeOffset.UtcNow, eventName, content)); } } } ================================================ FILE: src/Chapter02/ShoppingCart/EventFeed/EventsFeedModule.cs ================================================ namespace ShoppingCart.EventFeed { using Nancy; public class EventsFeedModule : NancyModule { public EventsFeedModule(IEventStore eventStore) : base("/events") { Get("/", _ => { long firstEventSequenceNumber, lastEventSequenceNumber; if (!long.TryParse(this.Request.Query.start.Value, out firstEventSequenceNumber)) firstEventSequenceNumber = 0; if (!long.TryParse(this.Request.Query.end.Value, out lastEventSequenceNumber)) lastEventSequenceNumber = long.MaxValue; return eventStore.GetEvents( firstEventSequenceNumber, lastEventSequenceNumber); }); } } } ================================================ FILE: src/Chapter02/ShoppingCart/EventFeed/IEventStore.cs ================================================ using System.Collections.Generic; using ShoppingCart.ShoppingCart; namespace ShoppingCart.EventFeed { public interface IEventStore { IEnumerable GetEvents(long firstEventSequenceNumber, long lastEventSequenceNumber); void Raise(string eventName, object content); } } ================================================ FILE: src/Chapter02/ShoppingCart/IProductCatalogueClient.cs ================================================ namespace ShoppingCart { using System.Collections.Generic; using System.Threading.Tasks; using ShoppingCart; public interface IProductCatalogueClient { Task> GetShoppingCartItems(int[] productCatalogueIds); } } ================================================ FILE: src/Chapter02/ShoppingCart/ProductCatalogueClient.cs ================================================ namespace ShoppingCart { using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Net.Http; using System.Threading; using Newtonsoft.Json; using Polly; using ShoppingCart; public class ProductCatalogueClient : IProductCatalogueClient { private static Policy exponentialRetryPolicy = Policy .Handle() .WaitAndRetryAsync( 3, attempt => TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt)), (ex, _) => Console.WriteLine(ex.ToString())); private static string productCatalogueBaseUrl = @"http://private-05cc8-chapter2productcataloguemicroservice.apiary-mock.com"; private static string getProductPathTemplate = "/products?productIds=[{0}]"; public Task> GetShoppingCartItems(int[] productCatalogueIds) => exponentialRetryPolicy .ExecuteAsync(async () => await GetItemsFromCatalogueService(productCatalogueIds).ConfigureAwait(false)); private async Task> GetItemsFromCatalogueService(int[] productCatalogueIds) { var response = await RequestProductFromProductCatalogue(productCatalogueIds).ConfigureAwait(false); return await ConvertToShoppingCartItems(response).ConfigureAwait(false); } private static async Task RequestProductFromProductCatalogue(int[] productCatalogueIds) { var productsResource = string.Format( getProductPathTemplate, string.Join(",", productCatalogueIds)); using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri(productCatalogueBaseUrl); return await httpClient.GetAsync(productsResource).ConfigureAwait(false); } } private static async Task> ConvertToShoppingCartItems(HttpResponseMessage response) { response.EnsureSuccessStatusCode(); var products = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync().ConfigureAwait(false)); return products .Select(p => new ShoppingCartItem( int.Parse(p.ProductId), p.ProductName, p.ProductDescription, p.Price )); } private class ProductCatalogueProduct { public string ProductId { get; set; } public string ProductName { get; set; } public string ProductDescription { get; set; } public Money Price { get; set; } } } } ================================================ FILE: src/Chapter02/ShoppingCart/Program.cs ================================================ using System.IO; using Microsoft.AspNetCore.Hosting; namespace ShoppingCart { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter02/ShoppingCart/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "ShoppingCart": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter02/ShoppingCart/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter02/ShoppingCart/ShoppingCart/IShoppingCartStore.cs ================================================ namespace ShoppingCart.ShoppingCart { public interface IShoppingCartStore { ShoppingCart Get(int userId); void Save(ShoppingCart shoppingCart); } } ================================================ FILE: src/Chapter02/ShoppingCart/ShoppingCart/ShoppingCart.cs ================================================ namespace ShoppingCart.ShoppingCart { using System; using System.Collections.Generic; using System.Linq; using EventFeed; public class ShoppingCart { private HashSet items = new HashSet(); public int UserId { get; } public IEnumerable Items { get { return items; } } public ShoppingCart(int userId) { this.UserId = userId; } public void AddItems( IEnumerable shoppingCartItems, IEventStore eventStore) { foreach (var item in shoppingCartItems) if (this.items.Add(item)) eventStore.Raise( "ShoppingCartItemAdded", new { UserId, item }); } public void RemoveItems( int[] productCatalogueIds, IEventStore eventStore) { items.RemoveWhere(i => productCatalogueIds.Contains(i.ProductCatalogueId)); } } public class ShoppingCartItem { public int ProductCatalogueId { get; } public string ProductName { get; } public string Desscription { get; } public Money Price { get; } public ShoppingCartItem( int productCatalogueId, string productName, string description, Money price) { this.ProductCatalogueId = productCatalogueId; this.ProductName = productName; this.Desscription = description; this.Price = price; } public override bool Equals(object obj) { if (obj == null || GetType() != obj.GetType()) { return false; } var that = obj as ShoppingCartItem; return this.ProductCatalogueId.Equals(that.ProductCatalogueId); } // override object.GetHashCode public override int GetHashCode() { return this.ProductCatalogueId.GetHashCode(); } } public class Money { public string Currency { get; } public decimal Amount { get; } public Money(string currency, decimal amount) { this.Currency = currency; this.Amount = amount; } } } ================================================ FILE: src/Chapter02/ShoppingCart/ShoppingCart/ShoppingCartModule.cs ================================================ namespace ShoppingCart.ShoppingCart { using EventFeed; using Nancy; using Nancy.ModelBinding; public class ShoppingCartModule : NancyModule { public ShoppingCartModule( IShoppingCartStore shoppingCartStore, IProductCatalogueClient productCatalogue, IEventStore eventStore) : base("/shoppingcart") { Get("/{userid:int}", parameters => { var userId = (int) parameters.userid; return shoppingCartStore.Get(userId); }); Post("/{userid:int}/items", async (parameters, _) => { var productCatalogueIds = this.Bind(); var userId = (int) parameters.userid; var shoppingCart = shoppingCartStore.Get(userId); var shoppingCartItems = await productCatalogue.GetShoppingCartItems(productCatalogueIds).ConfigureAwait(false); shoppingCart.AddItems(shoppingCartItems, eventStore); shoppingCartStore.Save(shoppingCart); return shoppingCart; }); Delete("/{userid:int}/items", parameters => { var productCatalogueIds = this.Bind(); var userId = (int)parameters.userid; var shoppingCart = shoppingCartStore.Get(userId); shoppingCart.RemoveItems(productCatalogueIds, eventStore); shoppingCartStore.Save(shoppingCart); return shoppingCart; }); } } } ================================================ FILE: src/Chapter02/ShoppingCart/ShoppingCart/ShoppingCartStore.cs ================================================ namespace ShoppingCart.ShoppingCart { using System.Collections.Generic; public class ShoppingCartStore : IShoppingCartStore { private static readonly Dictionary database = new Dictionary(); public ShoppingCart Get(int userId) { if (!database.ContainsKey(userId)) database[userId] = new ShoppingCart(userId); return database[userId]; } public void Save(ShoppingCart shoppingCart) { // Nothing needed. Saving would be needed with a real DB } } } ================================================ FILE: src/Chapter02/ShoppingCart/ShoppingCart.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 10e4f601-c16b-4936-a7b4-d32d799318d1 ShoppingCart .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter02/ShoppingCart/Startup.cs ================================================ namespace ShoppingCart { using Microsoft.AspNetCore.Builder; using Nancy; using Nancy.Configuration; using Nancy.Owin; public class Startup { public void Configure(IApplicationBuilder app) { app.UseOwin().UseNancy(opt => opt.Bootstrapper = new TracingBootstrapper()); } } public class TracingBootstrapper : Nancy.DefaultNancyBootstrapper { public override void Configure(INancyEnvironment env) { env.Tracing(enabled: true, displayErrorTraces: true); } } } ================================================ FILE: src/Chapter02/ShoppingCart/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Owin": "1.0.0", "System.Net.Http": "4.0.1", "Nancy": "2.0.0-barneyrubble", "Polly": "4.2.1" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "dnxcore50", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "HelloMicroservices" } } ================================================ FILE: src/Chapter02/ShoppingCart/web.config ================================================ ================================================ FILE: src/Chapter02/ch02.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ShoppingCart", "ShoppingCart\ShoppingCart.xproj", "{10E4F601-C16B-4936-A7B4-D32D799318D1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {10E4F601-C16B-4936-A7B4-D32D799318D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {10E4F601-C16B-4936-A7B4-D32D799318D1}.Debug|Any CPU.Build.0 = Debug|Any CPU {10E4F601-C16B-4936-A7B4-D32D799318D1}.Release|Any CPU.ActiveCfg = Release|Any CPU {10E4F601-C16B-4936-A7B4-D32D799318D1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: src/Chapter04/.idea.Ch4/riderModule.iml ================================================ ================================================ FILE: src/Chapter04/ApiGatewayMock/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter04/ApiGatewayMock/ApiGatewayMock.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) fe109a02-bf1b-46fc-87c8-2a68781ce8a4 ApiGatewayMock .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter04/ApiGatewayMock/LoyalProgramClient.cs ================================================ using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Polly; namespace ApiGatewayMock { public class LoyaltyProgramClient { private static Policy exponentialRetryPolicy = Policy .Handle() .WaitAndRetryAsync( 3, attempt => TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt)), (_, __) => Console.WriteLine("retrying..." + _) ); private string hostName; public LoyaltyProgramClient(string loyalProgramMicroserviceHostName) { this.hostName = loyalProgramMicroserviceHostName; } public async Task QueryUser(int userId) { return await exponentialRetryPolicy.ExecuteAsync(() => DoUserQuery(userId)); } private async Task DoUserQuery(int userId) { var userResource = $"/users/{userId}"; using(var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri($"http://{this.hostName}"); var response = await httpClient.GetAsync(userResource); ThrowOnTransientFailure(response); return response; } } private static void ThrowOnTransientFailure(HttpResponseMessage response) { if (((int)response.StatusCode) < 200 || ((int)response.StatusCode) > 499) throw new Exception(response.StatusCode.ToString()); } public async Task RegisterUser(LoyaltyProgramUser newUser) { return await exponentialRetryPolicy.ExecuteAsync(() => DoRegisterUser(newUser)); } private async Task DoRegisterUser(LoyaltyProgramUser newUser) { using(var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri($"http://{this.hostName}"); var response = await httpClient.PostAsync("/users/", new StringContent(JsonConvert.SerializeObject(newUser), Encoding.UTF8, "application/json")); ThrowOnTransientFailure(response); return response; } } public async Task UpdateUser(LoyaltyProgramUser user) { return await exponentialRetryPolicy.ExecuteAsync(() => DoUpdateUser(user)); } private async Task DoUpdateUser(LoyaltyProgramUser user) { using(var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri($"http://{this.hostName}"); var response = await httpClient.PutAsync($"/users/{user.Id}", new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8, "application/json")); ThrowOnTransientFailure(response); return response; } } } public class LoyaltyProgramUser { public int Id { get; set; } public string Name { get; set; } public int LoyaltyPoints { get; set; } public LoyaltyProgramSettings Settings { get; set; } } public class LoyaltyProgramSettings { public string[] Interests { get; set; } } } ================================================ FILE: src/Chapter04/ApiGatewayMock/Program.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; using static System.Console; namespace ApiGatewayMock { public class Program { private LoyaltyProgramClient client; public static void Main(string[] arg) => new Program().Main(); public void Main() { this.client = new LoyaltyProgramClient("localhost:5000"); WriteLine("Welcome to the API Gateway Mock."); var cont = true; while (cont) { WriteLine(); WriteLine(); WriteLine("********************"); WriteLine("Choose one of:"); WriteLine("q - to query the Loyalty Program Microservice for a user with id ."); WriteLine("r - to register a user with id with the Loyalty Program Microservice."); WriteLine("u - to update a user with new comman separated interests"); WriteLine("exit - to exit"); WriteLine("********************"); var cmd = ReadLine(); cont = ProcessCommand(cmd); } } private bool ProcessCommand(string cmd) { if ("exit".Equals(cmd)) return false; if (cmd.StartsWith("q")) ProcessUserQuery(cmd); else if (cmd.StartsWith("r")) ProcessUserRegistration(cmd); else if (cmd.StartsWith("u")) ProcessUpdateUser(cmd); else WriteLine("Did not understand command :("); return true; } private void ProcessUserQuery(string cmd) { int userId; if (!int.TryParse(cmd.Substring(1), out userId)) WriteLine("Please specify user id as an int"); else { var response = this.client.QueryUser(userId).Result; PrettyPrintResponse(response); } } private void ProcessUserRegistration(string cmd) { var newUser = new LoyaltyProgramUser { Name = cmd.Substring(1).Trim() }; var response = this.client.RegisterUser(newUser).Result; PrettyPrintResponse(response); } private static async void PrettyPrintResponse(HttpResponseMessage response) { WriteLine("Status code: " + response?.StatusCode.ToString() ?? "command failed"); WriteLine("Headers: " + response?.Headers.Aggregate("", (acc, h) => acc + "\n\t" + h.Key + ": " + h.Value) ?? ""); WriteLine("Body: " + await response?.Content.ReadAsStringAsync() ?? ""); } private async void ProcessUpdateUser(string cmd) { int userId; if (!int.TryParse(cmd.Split(' ').Skip(1).First(), out userId)) WriteLine("Plaese speciffy user id as an int"); else { var response = this.client.QueryUser(userId).Result; if (response.StatusCode == System.Net.HttpStatusCode.OK) { var user = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); var newInterests = cmd.Substring(cmd.IndexOf(' ', 2)).Split(',').Select(i => i.Trim()); user.Settings = new LoyaltyProgramSettings { Interests = user.Settings?.Interests.Union(newInterests).ToArray() ?? newInterests.ToArray() }; PrettyPrintResponse(this.client.UpdateUser(user).Result); } } } } } ================================================ FILE: src/Chapter04/ApiGatewayMock/project.json ================================================ { "version": "1.0.0-*", "buildOptions": { "emitEntryPoint": true }, "dependencies": { "Microsoft.NETCore.App": { "type": "platform", "version": "1.0.0" }, "Newtonsoft.Json": "8.0.3", "Polly": "4.2.1", "System.Net.Http": "4.0.1" }, "frameworks": { "netcoreapp1.0": { "imports": "dnxcore50" } }, "tooling": { "defaultNamespace": "ApiGatewayMock" } } ================================================ FILE: src/Chapter04/Ch4.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoyaltyProgram", "LoyaltyProgram\LoyaltyProgram.xproj", "{4BED3C45-E8C3-4345-A08F-249A39A256EB}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApiGatewayMock", "ApiGatewayMock\ApiGatewayMock.xproj", "{FE109A02-BF1B-46FC-87C8-2A68781CE8A4}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoyaltyProgramEventConsumer", "LoyaltyProgramEventConsumer\LoyaltyProgramEventConsumer.xproj", "{47349717-585A-43EE-96E8-7D5249CB431D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Release|Any CPU.Build.0 = Release|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Release|Any CPU.Build.0 = Release|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Release|Any CPU.Build.0 = Release|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Debug|Any CPU.Build.0 = Debug|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Release|Any CPU.ActiveCfg = Release|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: src/Chapter04/LoyaltyProgram/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter04/LoyaltyProgram/Bootstrapper.cs ================================================ namespace LoyaltyProgram { using System; using Nancy; using Nancy.Bootstrapper; public class Bootstrapper : DefaultNancyBootstrapper { protected override Func InternalConfiguration => NancyInternalConfiguration.WithOverrides(builder => builder.StatusCodeHandlers.Clear()); } } ================================================ FILE: src/Chapter04/LoyaltyProgram/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter04/LoyaltyProgram/LoyaltyProgram.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 4bed3c45-e8c3-4345-a08f-249a39a256eb LoyaltyProgram .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter04/LoyaltyProgram/Program.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; namespace LoyaltyProgram { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter04/LoyaltyProgram/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "LoyaltyProgram": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter04/LoyaltyProgram/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter04/LoyaltyProgram/Startup.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; namespace LoyaltyProgram { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } } ================================================ FILE: src/Chapter04/LoyaltyProgram/UsersModule.cs ================================================ namespace LoyaltyProgram { using System.Collections.Generic; using Nancy; using Nancy.ModelBinding; public class UsersModule : NancyModule { private static IDictionary registeredUsers = new Dictionary(); public UsersModule() : base("/users") { Get("/", _ => registeredUsers.Values); Get("/{userId:int}", parameters => { int userId = parameters.userId; if (registeredUsers.ContainsKey(userId)) return registeredUsers[userId]; else return HttpStatusCode.NotFound; }); Post("/", _ => { var newUser = this.Bind(); this.AddRegisteredUser(newUser); return this.CreatedResponse(newUser); }); Put("/{userId:int}", parameters => { int userId = parameters.userId; var updatedUser = this.Bind(); registeredUsers[userId] = updatedUser; return updatedUser; }); } private dynamic CreatedResponse(LoyaltyProgramUser newUser) { return this.Negotiate .WithStatusCode(HttpStatusCode.Created) .WithHeader("Location", this.Request.Url.SiteBase + "/users/" + newUser.Id) .WithModel(newUser); } private void AddRegisteredUser(LoyaltyProgramUser newUser) { var userId = registeredUsers.Count; newUser.Id = userId; registeredUsers[userId] = newUser; } } public class LoyaltyProgramUser { public int Id { get; set; } public string Name { get; set; } public int LoyaltyPoints { get; set; } public LoyaltyProgramSettings Settings { get; set; } } public class LoyaltyProgramSettings { public string[] Interests { get; set; } } } ================================================ FILE: src/Chapter04/LoyaltyProgram/YamlSerializerDeserializer.cs ================================================ namespace LoyaltyProgram { using System; using System.Collections.Generic; using System.IO; using Nancy; using Nancy.ModelBinding; using Nancy.Responses.Negotiation; using YamlDotNet.Serialization; public class YamlBodyDeserializer : IBodyDeserializer { public bool CanDeserialize(MediaRange mediaRange, BindingContext context) => mediaRange.Subtype.ToString().EndsWith("yaml"); public object Deserialize(MediaRange mediaRange, Stream bodyStream, BindingContext context) { var yamlDeserializer = new Deserializer(); var reader = new StreamReader(bodyStream); return yamlDeserializer.Deserialize(reader, context.DestinationType); } } public class YamlBodySerializer : IResponseProcessor { public IEnumerable> ExtensionMappings { get { yield return new Tuple("yaml", new MediaRange("application/yaml")); } } public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context) => requestedMediaRange.Subtype.ToString().EndsWith("yaml") ? new ProcessorMatch { ModelResult = MatchResult.DontCare, RequestedContentTypeResult = MatchResult.NonExactMatch} : ProcessorMatch.None; public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context) => new Response { Contents = stream => { var yamlSerializer = new Serializer(); var streamWriter = new StreamWriter(stream); yamlSerializer.Serialize(streamWriter, model); streamWriter.Flush(); }, ContentType = "application/yaml" }; } } ================================================ FILE: src/Chapter04/LoyaltyProgram/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Nancy": "2.0.0-barneyrubble", "YamlDotNet": "3.8.0-pre233" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "LoyaltyProgram" } } ================================================ FILE: src/Chapter04/LoyaltyProgram/web.config ================================================ ================================================ FILE: src/Chapter04/LoyaltyProgramEventConsumer/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter04/LoyaltyProgramEventConsumer/LoyaltyProgramEventConsumer.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 47349717-585a-43ee-96e8-7d5249cb431d LoyaltyProgramEventConsumer .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter04/LoyaltyProgramEventConsumer/Program.cs ================================================ namespace LoyaltyProgramEventConsumer { using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.ServiceProcess; using System.Threading.Tasks; using System.Timers; using Newtonsoft.Json; using static System.Console; public class EventSubscriber { private readonly string loyaltyProgramHost; private long start = 0, chunkSize = 100; private readonly Timer timer; public EventSubscriber(string loyaltyProgramHost) { WriteLine("created"); this.loyaltyProgramHost = loyaltyProgramHost; this.timer = new Timer(10 * 1000); this.timer.AutoReset = false; this.timer.Elapsed += (_, __) => SubscriptionCycleCallback().Wait(); } private async Task SubscriptionCycleCallback() { var response = await ReadEvents(); if (response.StatusCode == HttpStatusCode.OK) HandleEvents(await response.Content.ReadAsStringAsync()); this.timer.Start(); } private async Task ReadEvents() { using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri($"http://{this.loyaltyProgramHost}"); var response = await httpClient.GetAsync($"/events/?start={this.start}&end={this.start + this.chunkSize}"); PrettyPrintResponse(response); return response; } } private void HandleEvents(string content) { WriteLine("Handling events"); var events = JsonConvert.DeserializeObject>(content); WriteLine(events); WriteLine(events.Count()); foreach (var ev in events) { WriteLine(ev.Content); dynamic eventData = ev.Content; WriteLine("product name from data: " + (string)eventData.item.productName); this.start = Math.Max(this.start, ev.SequenceNumber + 1); } } public void Start() { this.timer.Start(); } public void Stop() { this.timer.Stop(); } private static async void PrettyPrintResponse(HttpResponseMessage response) { WriteLine("Status code: " + response?.StatusCode.ToString() ?? "command failed"); WriteLine("Headers: " + response?.Headers.Aggregate("", (acc, h) => acc + "\n\t" + h.Key + ": " + h.Value) ?? ""); WriteLine("Body: " + await response?.Content.ReadAsStringAsync() ?? ""); } } public struct Event { public long SequenceNumber { get; set; } public string Name { get; set; } public object Content { get; set; } } public class Program : ServiceBase { private EventSubscriber subscriber; public static void Main(string[] args) => new Program().Main(); public void Main() { this.subscriber = new EventSubscriber("localhost:5000"); //Run(this); OnStart(null); ReadLine(); } protected override void OnStart(string[] args) { this.subscriber.Start(); } protected override void OnStop() { this.subscriber.Stop(); } } } ================================================ FILE: src/Chapter04/LoyaltyProgramEventConsumer/project.json ================================================ { "version": "1.0.0-*", "buildOptions": { "emitEntryPoint": true }, "dependencies": { "Newtonsoft.Json": "8.0.3", "System.ServiceProcess.ServiceController": "4.1.0", "System.Net.Http": "4.1.0" }, "frameworks": { "net461": { } }, "tooling": { "defaultNamespace": "LoyaltyProgramEventConsumer" } } ================================================ FILE: src/Chapter05/Ch5.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ShoppingCart", "ShoppingCart\ShoppingCart.xproj", "{10E4F601-C16B-4936-A7B4-D32D799318D1}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ProductCatalog", "ProductCatalog\ProductCatalog.xproj", "{46381F82-A705-496C-A0DF-7406EB036A22}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {10E4F601-C16B-4936-A7B4-D32D799318D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {10E4F601-C16B-4936-A7B4-D32D799318D1}.Debug|Any CPU.Build.0 = Debug|Any CPU {10E4F601-C16B-4936-A7B4-D32D799318D1}.Release|Any CPU.ActiveCfg = Release|Any CPU {10E4F601-C16B-4936-A7B4-D32D799318D1}.Release|Any CPU.Build.0 = Release|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Release|Any CPU.Build.0 = Release|Any CPU {46381F82-A705-496C-A0DF-7406EB036A22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {46381F82-A705-496C-A0DF-7406EB036A22}.Debug|Any CPU.Build.0 = Debug|Any CPU {46381F82-A705-496C-A0DF-7406EB036A22}.Release|Any CPU.ActiveCfg = Release|Any CPU {46381F82-A705-496C-A0DF-7406EB036A22}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: src/Chapter05/ProductCatalog/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter05/ProductCatalog/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter05/ProductCatalog/ProductCatalog.xproj ================================================  14.0.25123 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 46381f82-a705-496c-a0df-7406eb036a22 ProductCatalog .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter05/ProductCatalog/ProductsModule.cs ================================================ namespace ProductCatalog { using System; using System.Collections.Generic; using System.Linq; using Nancy; public class ProductsModule : NancyModule { public ProductsModule(ProductStore productStore) : base("/products") { Get("", _ => { string productIdsString = this.Request.Query.productIds; var productIds = ParseProductIdsFromQueryString(productIdsString); var products = productStore.GetProductsByIds(productIds); return this .Negotiate .WithModel(products) .WithHeader("cache-control", "max-age:86400"); }); } private static IEnumerable ParseProductIdsFromQueryString(string productIdsString) { return productIdsString.Split(',').Select(s => s.Replace("[", "").Replace("]", "")).Select(int.Parse); } } public interface ProductStore { IEnumerable GetProductsByIds(IEnumerable productIds); } public class StaticProductStore : ProductStore { public IEnumerable GetProductsByIds(IEnumerable productIds) { return productIds.Select(id => new ProductCatalogProduct(id, "foo" + id, "bar", new Money())); } } public class ProductCatalogProduct { public ProductCatalogProduct(int productId, string productName, string description, Money price) { this.ProductId = productId.ToString(); this.ProductName = productName; this.ProductDescription = description; this.Price = price; } public string ProductId { get; private set; } public string ProductName { get; private set; } public string ProductDescription { get; private set; } public Money Price { get; private set; } } public class Money { } } ================================================ FILE: src/Chapter05/ProductCatalog/Program.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; namespace ProductCatalog { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter05/ProductCatalog/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "ProductCatalog": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter05/ProductCatalog/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter05/ProductCatalog/Startup.cs ================================================ namespace ProductCatalog { using Microsoft.AspNetCore.Builder; using Nancy.Owin; public class Startup { public void Configure(IApplicationBuilder app) { app.UseOwin().UseNancy(); } } } ================================================ FILE: src/Chapter05/ProductCatalog/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspnetCore.Owin": "1.0.0", "Nancy": "2.0.0-barneyrubble" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "ProductCatalog" } } ================================================ FILE: src/Chapter05/ProductCatalog/web.config ================================================ ================================================ FILE: src/Chapter05/ShoppingCart/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter05/ShoppingCart/.vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "name": ".NET Core Launch (console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", "args": [], "cwd": "${workspaceRoot}", "stopAtEntry": false }, { "name": ".NET Core Launch (web)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", "args": [], "cwd": "${workspaceRoot}", "stopAtEntry": false, "launchBrowser": { "enabled": true, "args": "${auto-detect-url}", "windows": { "command": "cmd.exe", "args": "/C start ${auto-detect-url}" }, "osx": { "command": "open" }, "linux": { "command": "xdg-open" } } }, { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", "processName": "" } ] } ================================================ FILE: src/Chapter05/ShoppingCart/.vscode/tasks.json ================================================ { "version": "0.1.0", "command": "dotnet", "isShellCommand": true, "args": [], "tasks": [ { "taskName": "build", "args": [], "isBuildCommand": true, "problemMatcher": "$msCompile" } ] } ================================================ FILE: src/Chapter05/ShoppingCart/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter05/ShoppingCart/EventFeed/Event.cs ================================================ namespace ShoppingCart.EventFeed { using System; public struct Event { public long SequenceNumber { get; } public DateTimeOffset OccurredAt { get; } public string Name { get; } public object Content { get; } public Event( long sequenceNumber, DateTimeOffset occurredAt, string name, object content) { this.SequenceNumber = sequenceNumber; this.OccurredAt = occurredAt; this.Name = name; this.Content = content; } } } ================================================ FILE: src/Chapter05/ShoppingCart/EventFeed/EventStore.cs ================================================ namespace ShoppingCart.EventFeed { using System; using System.Text; using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; using System.Threading.Tasks; using Dapper; using Newtonsoft.Json; #if net461 using global::EventStore.ClientAPI; #endif public class EventStore : IEventStore { private static long currentSequenceNumber = 0; private static readonly IList database = new List(); #if net461 private const string connectionString = "ConnectTo=discover://admin:changeit@127.0.0.1:2112/"; private IEventStoreConnection connection = EventStoreConnection.Create(connectionString); public async Task Raise(string eventName, object content) { await connection.ConnectAsync().ConfigureAwait(false); var contentJson = JsonConvert.SerializeObject(content); var metaDataJson = JsonConvert.SerializeObject(new EventMetadata { OccurredAt = DateTimeOffset.Now, EventName = eventName }); var eventData = new EventData( Guid.NewGuid(), "ShoppingCartEvent", isJson: true, data: Encoding.UTF8.GetBytes(contentJson), metadata: Encoding.UTF8.GetBytes(metaDataJson) ); await connection.AppendToStreamAsync( "ShoppingCart", ExpectedVersion.Any, eventData); } public async Task> GetEvents( long firstEventSequenceNumber, long lastEventSequenceNumber) { await connection.ConnectAsync().ConfigureAwait(false); var result = await connection.ReadStreamEventsForwardAsync( "ShoppingCart", start:(int) firstEventSequenceNumber, count: (int) (lastEventSequenceNumber - firstEventSequenceNumber), resolveLinkTos: false).ConfigureAwait(false); return result.Events .Select(ev => new { Content = JsonConvert.DeserializeObject( Encoding.UTF8.GetString(ev.Event.Data)), Metadata = JsonConvert.DeserializeObject( Encoding.UTF8.GetString(ev.Event.Data)) }) .Select((ev, i) => new Event( i + firstEventSequenceNumber, ev.Metadata.OccurredAt, ev.Metadata.EventName, ev.Content)); } private class EventMetadata { public DateTimeOffset OccurredAt { get; set; } public string EventName { get; set; } } #else private string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=ShoppingCart;Integrated Security=True"; private const string writeEventSql = @"insert into EventStore(Name, OccurredAt, Content) values (@Name, @OccurredAt, @Content)"; public Task Raise(string eventName, object content) { var jsonContent = JsonConvert.SerializeObject(content); using (var conn = new SqlConnection(connectionString)) { return conn.ExecuteAsync( writeEventSql, new { Name = eventName, OccurredAt = DateTimeOffset.Now, Content = jsonContent }); } } private const string readEventsSql = @"select * from EventStore where ID >= @Start and ID <= @End"; public async Task> GetEvents( long firstEventSequenceNumber, long lastEventSequenceNumber) { using (var conn = new SqlConnection(connectionString)) { return (await conn.QueryAsync( readEventsSql, new { Start = firstEventSequenceNumber, End = lastEventSequenceNumber }).ConfigureAwait(false)) .Select(row => { var content = JsonConvert.DeserializeObject(row.Content); return new Event(row.ID, row.OccurredAt, row.Name, content); }); } } #endif } } ================================================ FILE: src/Chapter05/ShoppingCart/EventFeed/EventsFeedModule.cs ================================================ namespace ShoppingCart.EventFeed { using Nancy; public class EventsFeedModule : NancyModule { public EventsFeedModule(IEventStore eventStore) : base("/events") { Get("/", _ => { long firstEventSequenceNumber, lastEventSequenceNumber; if (!long.TryParse(this.Request.Query.start.Value, out firstEventSequenceNumber)) firstEventSequenceNumber = 0; if (!long.TryParse(this.Request.Query.end.Value, out lastEventSequenceNumber)) lastEventSequenceNumber = long.MaxValue; return eventStore.GetEvents( firstEventSequenceNumber, lastEventSequenceNumber); }); } } } ================================================ FILE: src/Chapter05/ShoppingCart/EventFeed/IEventStore.cs ================================================ namespace ShoppingCart.EventFeed { using System.Collections.Generic; using System.Threading.Tasks; public interface IEventStore { Task> GetEvents(long firstEventSequenceNumber, long lastEventSequenceNumber); Task Raise(string eventName, object content); } } ================================================ FILE: src/Chapter05/ShoppingCart/ICache.cs ================================================ namespace ShoppingCart { using System; using System.Collections.Concurrent; using System.Collections.Generic; public interface ICache { void Add(string key, object value, TimeSpan ttl); object Get(string productsResource); } public class Cache : ICache { private static IDictionary> cache = new ConcurrentDictionary>(); public void Add(string key, object value, TimeSpan ttl) { cache[key] = Tuple.Create(DateTimeOffset.UtcNow.Add(ttl), value); } public object Get(string productsResource) { Tuple value; if (cache.TryGetValue(productsResource, out value) && value.Item1 > DateTimeOffset.UtcNow) return value; cache.Remove(productsResource); return null; } } } ================================================ FILE: src/Chapter05/ShoppingCart/IProductCatalogueClient.cs ================================================ namespace ShoppingCart { using System.Collections.Generic; using System.Threading.Tasks; using ShoppingCart; public interface IProductCatalogueClient { Task> GetShoppingCartItems(int[] productCatalogueIds); } } ================================================ FILE: src/Chapter05/ShoppingCart/ProductCatalogueClient.cs ================================================ namespace ShoppingCart { using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Net.Http; using System.Threading; using Newtonsoft.Json; using Polly; using ShoppingCart; using System.Net.Http.Headers; public class ProductCatalogueClient : IProductCatalogueClient { private static Policy exponentialRetryPolicy = Policy .Handle() .WaitAndRetryAsync( 3, attempt => TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt)), (ex, _) => Console.WriteLine(ex.ToString())); private static string productCatalogueBaseUrl = @"http://private-05cc8-chapter2productcataloguemicroservice.apiary-mock.com"; private static string getProductPathTemplate = "/products?productIds=[{0}]"; private readonly ICache cache; public ProductCatalogueClient(ICache cache) { this.cache = cache; } public Task> GetShoppingCartItems(int[] productCatalogueIds) => exponentialRetryPolicy .ExecuteAsync(() => GetItemsFromCatalogueService(productCatalogueIds)); private async Task> GetItemsFromCatalogueService(int[] productCatalogueIds) { var response = await RequestProductFromProductCatalogue(productCatalogueIds).ConfigureAwait(false); return await ConvertToShoppingCartItems(response).ConfigureAwait(false); } private async Task RequestProductFromProductCatalogue(int[] productCatalogueIds) { var productsResource = string.Format( getProductPathTemplate, string.Join(",", productCatalogueIds)); var response = this.cache.Get(productsResource) as HttpResponseMessage; if (response == null) { using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri(productCatalogueBaseUrl); response = await httpClient.GetAsync(productsResource).ConfigureAwait(false); AddToCache(productsResource, response); } } return response; } private void AddToCache(string resource, HttpResponseMessage response) { var cacheHeader = response .Headers .FirstOrDefault(h => h.Key == "cache-control"); if (string.IsNullOrEmpty(cacheHeader.Key)) return; var maxAge = CacheControlHeaderValue.Parse(cacheHeader.Value.ToString()) .MaxAge; if (maxAge.HasValue) this.cache.Add(key: resource, value: response, ttl: maxAge.Value); } private static async Task> ConvertToShoppingCartItems(HttpResponseMessage response) { response.EnsureSuccessStatusCode(); var products = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync().ConfigureAwait(false)); return products .Select(p => new ShoppingCartItem( int.Parse(p.ProductId), p.ProductName, p.ProductDescription, p.Price )); } private class ProductCatalogueProduct { public string ProductId { get; set; } public string ProductName { get; set; } public string ProductDescription { get; set; } public Money Price { get; set; } } } } ================================================ FILE: src/Chapter05/ShoppingCart/Program.cs ================================================ using System.IO; using Microsoft.AspNetCore.Hosting; namespace ShoppingCart { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter05/ShoppingCart/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "ShoppingCart": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter05/ShoppingCart/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter05/ShoppingCart/ShoppingCart/IShoppingCartStore.cs ================================================ namespace ShoppingCart.ShoppingCart { using System.Threading.Tasks; public interface IShoppingCartStore { Task Get(int userId); Task Save(ShoppingCart shoppingCart); } } ================================================ FILE: src/Chapter05/ShoppingCart/ShoppingCart/ShoppingCart.cs ================================================ namespace ShoppingCart.ShoppingCart { using System.Collections.Generic; using System.Linq; using global::ShoppingCart.EventFeed; public class ShoppingCart { private HashSet items = new HashSet(); public int UserId { get; } public IEnumerable Items { get { return items; } } public ShoppingCart(int userId) { this.UserId = userId; } public ShoppingCart(int userId, IEnumerable items) { this.UserId = userId; foreach (var item in items) { this.items.Add(item); } } public void AddItems( IEnumerable shoppingCartItems, IEventStore eventStore) { foreach (var item in shoppingCartItems) if (this.items.Add(item)) eventStore.Raise( "ShoppingCartItemAdded", new { UserId, item }); } public void RemoveItems( int[] productCatalogueIds, IEventStore eventStore) { items.RemoveWhere(i => productCatalogueIds.Contains(i.ProductCatalogueId)); } } public class ShoppingCartItem { public int ProductCatalogueId { get; } public string ProductName { get; } public string Desscription { get; } public Money Price { get; } public ShoppingCartItem( int productCatalogueId, string productName, string description, Money price) { this.ProductCatalogueId = productCatalogueId; this.ProductName = productName; this.Desscription = description; this.Price = price; } public override bool Equals(object obj) { if (obj == null || GetType() != obj.GetType()) { return false; } var that = obj as ShoppingCartItem; return this.ProductCatalogueId.Equals(that.ProductCatalogueId); } // override object.GetHashCode public override int GetHashCode() { return this.ProductCatalogueId.GetHashCode(); } } public class Money { public string Currency { get; } public decimal Amount { get; } public Money(string currency, decimal amount) { this.Currency = currency; this.Amount = amount; } } } ================================================ FILE: src/Chapter05/ShoppingCart/ShoppingCart/ShoppingCartModule.cs ================================================ namespace ShoppingCart.ShoppingCart { using EventFeed; using Nancy; using Nancy.ModelBinding; public class ShoppingCartModule : NancyModule { public ShoppingCartModule( IShoppingCartStore shoppingCartStore, IProductCatalogueClient productCatalogue, IEventStore eventStore) : base("/shoppingcart") { Get("/{userid:int}", parameters => { var userId = (int) parameters.userid; return shoppingCartStore.Get(userId); }); Post("/{userid:int}/items", async parameters => { var productCatalogueIds = this.Bind(); var userId = (int) parameters.userid; var shoppingCart = await shoppingCartStore.Get(userId).ConfigureAwait(false); var shoppingCartItems = await productCatalogue.GetShoppingCartItems(productCatalogueIds).ConfigureAwait(false); shoppingCart.AddItems(shoppingCartItems, eventStore); shoppingCartStore.Save(shoppingCart); return shoppingCart; }); Delete("/{userid:int}/items", async parameters => { var productCatalogueIds = this.Bind(); var userId = (int)parameters.userid; var shoppingCart = await shoppingCartStore.Get(userId).ConfigureAwait(false); shoppingCart.RemoveItems(productCatalogueIds, eventStore); shoppingCartStore.Save(shoppingCart); return shoppingCart; }); } } } ================================================ FILE: src/Chapter05/ShoppingCart/ShoppingCart/ShoppingCartStore.cs ================================================ namespace ShoppingCart.ShoppingCart { using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Threading.Tasks; using Dapper; public class ShoppingCartStore : IShoppingCartStore { private string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=ShoppingCart; Integrated Security=True"; private const string readItemsSql = @"select * from ShoppingCart, ShoppingCartItems where ShoppingCartItems.ShoppingCartId = ID and ShoppingCart.UserId=@UserId"; public async Task Get(int userId) { using (var conn = new SqlConnection(connectionString)) { var items = await conn.QueryAsync( readItemsSql, new { UserId = userId }); return new ShoppingCart(userId, items); } } private const string deleteAllForShoppingCartSql= @"delete item from ShoppingCartItems item inner join ShoppingCart cart on item.ShoppingCartId = cart.ID and cart.UserId=@UserId"; private const string addAllForShoppingCartSql= @"insert into ShoppingCartItems (ShoppingCartId, ProductCatalogId, ProductName, ProductDescription, Amount, Currency) values (@ShoppingCartId, @ProductCatalogId, @ProductName,v @ProductDescription, @Amount, @Currency)"; public async Task Save(ShoppingCart shoppingCart) { using (var conn = new SqlConnection(connectionString)) using (var tx = conn.BeginTransaction()) { await conn.ExecuteAsync( deleteAllForShoppingCartSql, new { UserId = shoppingCart.UserId }, tx).ConfigureAwait(false); await conn.ExecuteAsync( addAllForShoppingCartSql, shoppingCart.Items, tx).ConfigureAwait(false); } } } } ================================================ FILE: src/Chapter05/ShoppingCart/ShoppingCart.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 10e4f601-c16b-4936-a7b4-d32d799318d1 ShoppingCart .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter05/ShoppingCart/Startup.cs ================================================ namespace ShoppingCart { using Microsoft.AspNetCore.Builder; using Nancy; using Nancy.Configuration; using Nancy.Owin; public class Startup { public void Configure(IApplicationBuilder app) { app.UseOwin().UseNancy(opt => opt.Bootstrapper = new TracingBootstrapper()); } } public class TracingBootstrapper : Nancy.DefaultNancyBootstrapper { public override void Configure(INancyEnvironment env) { env.Tracing(enabled: true, displayErrorTraces: true); } } } ================================================ FILE: src/Chapter05/ShoppingCart/database-scripts/create-shopping-cart-db.sql ================================================ CREATE DATABASE ShoppingCart GO USE [ShoppingCart] GO CREATE TABLE [dbo].[ShoppingCart]( [ID] int IDENTITY(1,1) PRIMARY KEY, [UserId] [bigint] NOT NULL, CONSTRAINT ShoppingCartUnique UNIQUE([ID], [UserID]) ) GO CREATE INDEX ShoppingCart_UserId ON [dbo].[ShoppingCart] (UserId) GO CREATE TABLE [dbo].[ShoppingCartItems]( [ID] int IDENTITY(1,1) PRIMARY KEY, [ShoppingCartId] [int] NOT NULL, [ProductCatalogId] [bigint] NOT NULL, [ProductName] [nvarchar](100) NOT NULL, [ProductDescription] [nvarchar](500) NULL, [Amount] [int] NOT NULL, [Currency] [nvarchar](5) NOT NULL ) GO ALTER TABLE [dbo].[ShoppingCartItems] WITH CHECK ADD CONSTRAINT [FK_ShoppingCart] FOREIGN KEY([ShoppingCartId]) REFERENCES [dbo].[ShoppingCart] ([Id]) GO ALTER TABLE [dbo].[ShoppingCartItems] CHECK CONSTRAINT [FK_ShoppingCart] GO CREATE INDEX ShoppingCartItems_ShoppingCartId ON [dbo].[ShoppingCartItems] (ShoppingCartId) GO CREATE TABLE [dbo].[EventStore]( [ID] int IDENTITY(1,1) PRIMARY KEY, [Name] [nvarchar](100) NOT NULL, [OccurredAt] [datetimeoffset] NOT NULL, [Content][nvarchar](max) NOT NULL ) GO ================================================ FILE: src/Chapter05/ShoppingCart/project.json ================================================ { "dependencies": { "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Owin": "1.0.0", "Nancy": "2.0.0-barneyrubble", "Polly": "4.2.1", "Dapper": "1.50.0-rc2a" }, "runtimes": { "win10-x64": "" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ], "dependencies": { "System.Net.Http": "4.0.1" } }, "net461": { "compilationOptions": {"define": ["net461"]}, "frameworkAssemblies": { "System.Net.Http": "4.0.0.0", "System.Runtime": "4.0.20.0" }, "dependencies": { "EventStore.Client": "3.3.1", "Microsoft.CSharp": "4.0.0" } } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "HelloMicroservices" } } ================================================ FILE: src/Chapter05/ShoppingCart/web.config ================================================ ================================================ FILE: src/Chapter06/ApiGatewayMock/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter06/ApiGatewayMock/ApiGatewayMock.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) fe109a02-bf1b-46fc-87c8-2a68781ce8a4 ApiGatewayMock .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter06/ApiGatewayMock/LoyaltyProgramClient.cs ================================================ using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Polly; namespace ApiGatewayMock { public class LoyaltyProgramClient { private static Policy exponentialRetryPolicy = Policy .Handle() .WaitAndRetryAsync( 3, attempt => TimeSpan.FromMilliseconds(100*Math.Pow(2, attempt)), (_, __) => Console.WriteLine("retrying..." + _) ); private static Policy circuitBreaker = Policy .Handle() .CircuitBreaker(5, TimeSpan.FromMinutes(5)); private string hostName; public LoyaltyProgramClient(string loyalProgramMicroserviceHostName) { this.hostName = loyalProgramMicroserviceHostName; } public async Task QueryUser(int userId) { return await circuitBreaker.ExecuteAsync(() => DoUserQuery(userId)); } private async Task DoUserQuery(int userId) { var userResource = $"/users/{userId}"; using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri($"http://{this.hostName}"); var response = await httpClient.GetAsync(userResource); ThrowOnTransientFailure(response); return response; } } private static void ThrowOnTransientFailure(HttpResponseMessage response) { if (((int) response.StatusCode) < 200 || ((int) response.StatusCode) > 499) throw new Exception(response.StatusCode.ToString()); } public async Task RegisterUser(LoyaltyProgramUser newUser) { return await exponentialRetryPolicy.ExecuteAsync(() => DoRegisterUser(newUser)); } private async Task DoRegisterUser(LoyaltyProgramUser newUser) { using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri($"http://{this.hostName}"); var response = await httpClient.PostAsync("/users/", new StringContent(JsonConvert.SerializeObject(newUser), Encoding.UTF8, "application/json")); ThrowOnTransientFailure(response); return response; } } public async Task UpdateUser(LoyaltyProgramUser user) { return await exponentialRetryPolicy.ExecuteAsync(() => DoUpdateUser(user)); } private async Task DoUpdateUser(LoyaltyProgramUser user) { using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri($"http://{this.hostName}"); var response = await httpClient.PutAsync($"/users/{user.Id}", new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8, "application/json")); ThrowOnTransientFailure(response); return response; } } } public class LoyaltyProgramUser { public int Id { get; set; } public string Name { get; set; } public int LoyaltyPoints { get; set; } public LoyaltyProgramSettings Settings { get; set; } } public class LoyaltyProgramSettings { public string[] Interests { get; set; } } } ================================================ FILE: src/Chapter06/ApiGatewayMock/Program.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; using static System.Console; namespace ApiGatewayMock { public class Program { private LoyaltyProgramClient client; public static void Main(string[] arg) => new Program().Main(); public void Main() { this.client = new LoyaltyProgramClient("localhost:5000"); WriteLine("Welcome to the API Gateway Mock."); var cont = true; while (cont) { WriteLine(); WriteLine(); WriteLine("********************"); WriteLine("Choose one of:"); WriteLine("q - to query the Loyalty Program Microservice for a user with id ."); WriteLine("r - to register a user with id with the Loyalty Program Microservice."); WriteLine("u - to update a user with new comman separated interests"); WriteLine("exit - to exit"); WriteLine("********************"); var cmd = ReadLine(); cont = ProcessCommand(cmd); } } private bool ProcessCommand(string cmd) { if ("exit".Equals(cmd)) return false; if (cmd.StartsWith("q")) ProcessUserQuery(cmd); else if (cmd.StartsWith("r")) ProcessUserRegistration(cmd); else if (cmd.StartsWith("u")) ProcessUpdateUser(cmd); else WriteLine("Did not understand command :("); return true; } private void ProcessUserQuery(string cmd) { int userId; if (!int.TryParse(cmd.Substring(1), out userId)) WriteLine("Please specify user id as an int"); else { var response = this.client.QueryUser(userId).Result; PrettyPrintResponse(response); } } private void ProcessUserRegistration(string cmd) { var newUser = new LoyaltyProgramUser { Name = cmd.Substring(1).Trim() }; var response = this.client.RegisterUser(newUser).Result; PrettyPrintResponse(response); } private static async void PrettyPrintResponse(HttpResponseMessage response) { WriteLine("Status code: " + response?.StatusCode.ToString() ?? "command failed"); WriteLine("Headers: " + response?.Headers.Aggregate("", (acc, h) => acc + "\n\t" + h.Key + ": " + h.Value) ?? ""); WriteLine("Body: " + await response?.Content.ReadAsStringAsync() ?? ""); } private async void ProcessUpdateUser(string cmd) { int userId; if (!int.TryParse(cmd.Split(' ').Skip(1).First(), out userId)) WriteLine("Plaese speciffy user id as an int"); else { var response = this.client.QueryUser(userId).Result; if (response.StatusCode == System.Net.HttpStatusCode.OK) { var user = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); var newInterests = cmd.Substring(cmd.IndexOf(' ', 2)).Split(',').Select(i => i.Trim()); user.Settings = new LoyaltyProgramSettings { Interests = user.Settings?.Interests.Union(newInterests).ToArray() ?? newInterests.ToArray() }; PrettyPrintResponse(this.client.UpdateUser(user).Result); } } } } } ================================================ FILE: src/Chapter06/ApiGatewayMock/project.json ================================================ { "version": "1.0.0-*", "buildOptions": { "emitEntryPoint": true }, "dependencies": { "Microsoft.NETCore.App": { "type": "platform", "version": "1.0.0" }, "Newtonsoft.Json": "8.0.3", "Polly": "4.2.1", "System.Net.Http": "4.0.1" }, "frameworks": { "netcoreapp1.0": { "imports": "dnxcore50" } }, "tooling": { "defaultNamespace": "ApiGatewayMock" } } ================================================ FILE: src/Chapter06/Ch6.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoyaltyProgram", "LoyaltyProgram\LoyaltyProgram.xproj", "{4BED3C45-E8C3-4345-A08F-249A39A256EB}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApiGatewayMock", "ApiGatewayMock\ApiGatewayMock.xproj", "{FE109A02-BF1B-46FC-87C8-2A68781CE8A4}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoyaltyProgramEventConsumer", "LoyaltyProgramEventConsumer\LoyaltyProgramEventConsumer.xproj", "{47349717-585A-43EE-96E8-7D5249CB431D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Release|Any CPU.Build.0 = Release|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Release|Any CPU.Build.0 = Release|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Release|Any CPU.Build.0 = Release|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Debug|Any CPU.Build.0 = Debug|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Release|Any CPU.ActiveCfg = Release|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: src/Chapter06/LoyaltyProgram/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter06/LoyaltyProgram/Bootstrapper.cs ================================================ namespace LoyaltyProgram { using System; using Nancy; using Nancy.Bootstrapper; using Nancy.TinyIoc; public class Bootstrapper : DefaultNancyBootstrapper { protected override Func InternalConfiguration => NancyInternalConfiguration.WithOverrides(builder => builder.StatusCodeHandlers.Clear()); protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) { pipelines.OnError += (ctx, ex) => { // write to central log store return null; }; } } } ================================================ FILE: src/Chapter06/LoyaltyProgram/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter06/LoyaltyProgram/LoyaltyProgram.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 4bed3c45-e8c3-4345-a08f-249a39a256eb LoyaltyProgram .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter06/LoyaltyProgram/Program.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; namespace LoyaltyProgram { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter06/LoyaltyProgram/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "LoyaltyProgram": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter06/LoyaltyProgram/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter06/LoyaltyProgram/Startup.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; namespace LoyaltyProgram { public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app) { app.Run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); } } } ================================================ FILE: src/Chapter06/LoyaltyProgram/UsersModule.cs ================================================ using System.Collections.Generic; using Nancy; using Nancy.ModelBinding; namespace LoyaltyProgram { public class UsersModule : NancyModule { private static IDictionary registerUsers = new Dictionary(); public UsersModule() : base("/users") { Get("/", _ => registerUsers.Values); Get("/{userId:int}", parameters => { int userId = parameters.userId; if (registerUsers.ContainsKey(userId)) return registerUsers[userId]; else return HttpStatusCode.NotFound; }); Post("/", _ => { var newUser = this.Bind(); this.AddRegisteredUser(newUser); return this.CreatedResponse(newUser); }); Put("/{userId:int}", parameters => { int userId = parameters.userId; var updatedUser = this.Bind(); registerUsers[userId] = updatedUser; return updatedUser; }); } private dynamic CreatedResponse(LoyaltyProgramUser newUser) { return this.Negotiate .WithStatusCode(HttpStatusCode.Created) .WithHeader("Location", this.Request.Url.SiteBase + "/users/" + newUser.Id) .WithModel(newUser); } private void AddRegisteredUser(LoyaltyProgramUser newUser) { var userId = registerUsers.Count; newUser.Id = userId; registerUsers[userId] = newUser; } } public class LoyaltyProgramUser { public int Id { get; set; } public string Name { get; set; } public int LoyaltyPoints { get; set; } public LoyaltyProgramSettings Settings { get; set; } } public class LoyaltyProgramSettings { public string[] Interests { get; set; } } } ================================================ FILE: src/Chapter06/LoyaltyProgram/YamlSerializerDeserializer.cs ================================================ namespace LoyaltyProgram { using System; using System.Collections.Generic; using System.IO; using Nancy; using Nancy.ModelBinding; using Nancy.Responses.Negotiation; using YamlDotNet.Serialization; public class YamlBodyDeserializer : IBodyDeserializer { public bool CanDeserialize(MediaRange mediaRange, BindingContext context) => mediaRange.Subtype.ToString().EndsWith("yaml"); public object Deserialize(MediaRange mediaRange, Stream bodyStream, BindingContext context) { var yamlDeserializer = new Deserializer(); var reader = new StreamReader(bodyStream); return yamlDeserializer.Deserialize(reader, context.DestinationType); } } public class YamlBodySerializer : IResponseProcessor { public IEnumerable> ExtensionMappings { get { yield return new Tuple("yaml", new MediaRange("application/yaml")); } } public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context) => requestedMediaRange.Subtype.ToString().EndsWith("yaml") ? new ProcessorMatch { ModelResult = MatchResult.DontCare, RequestedContentTypeResult = MatchResult.NonExactMatch} : ProcessorMatch.None; public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context) => new Response { Contents = stream => { var yamlSerializer = new Serializer(); var streamWriter = new StreamWriter(stream); yamlSerializer.Serialize(streamWriter, model); streamWriter.Flush(); }, ContentType = "application/yaml" }; } } ================================================ FILE: src/Chapter06/LoyaltyProgram/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Nancy": "2.0.0-barneyrubble", "YamlDotNet": "3.8.0-pre233" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": { "version": "1.0.0-preview1-final", "imports": "portable-net45+win8+dnxcore50" } }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "LoyaltyProgram" } } ================================================ FILE: src/Chapter06/LoyaltyProgram/web.config ================================================ ================================================ FILE: src/Chapter06/LoyaltyProgramEventConsumer/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter06/LoyaltyProgramEventConsumer/LoyaltyProgramEventConsumer.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 47349717-585a-43ee-96e8-7d5249cb431d LoyaltyProgramEventConsumer .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter06/LoyaltyProgramEventConsumer/Program.cs ================================================ using static System.Console; namespace LoyaltyProgramEventConsumer { using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.ServiceProcess; using System.Threading.Tasks; using System.Timers; using Newtonsoft.Json; public class EventSubscriber { private readonly string loyaltyProgramHost; private long start = 0, chunkSize = 100; private readonly Timer timer; public EventSubscriber(string loyaltyProgramHost) { WriteLine("created"); this.loyaltyProgramHost = loyaltyProgramHost; this.timer = new Timer(10*1000); this.timer.AutoReset = false; this.timer.Elapsed += (_, __) => SubscriptionCycleCallback().Wait(); } private async Task SubscriptionCycleCallback() { var response = await ReadEvents().ConfigureAwait(false); if (response.StatusCode == HttpStatusCode.OK) HandleEvents(await response.Content.ReadAsStringAsync().ConfigureAwait(false)); this.timer.Start(); } private async Task ReadEvents() { using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri($"http://{this.loyaltyProgramHost}"); var resource = $"/events/?start={this.start}&end={this.start + this.chunkSize}"; var response = await httpClient.GetAsync(resource).ConfigureAwait(false); PrettyPrintResponse(response); return response; } } private void HandleEvents(string content) { WriteLine("Handling events"); var events = JsonConvert.DeserializeObject>(content); WriteLine(events); WriteLine(events.Count()); foreach (var ev in events) { WriteLine(ev.Content); dynamic eventData = ev.Content; WriteLine("product name from data: " + (string) eventData.item.productName); this.start = Math.Max(this.start, ev.SequenceNumber + 1); } } public void Start() { this.timer.Start(); } public void Stop() { this.timer.Stop(); } private static async void PrettyPrintResponse(HttpResponseMessage response) { WriteLine("Status code: " + response?.StatusCode.ToString() ?? "command failed"); WriteLine("Headers: " + response?.Headers.Aggregate("", (acc, h) => acc + "\n\t" + h.Key + ": " + h.Value) ?? ""); WriteLine("Body: " + await response?.Content.ReadAsStringAsync() ?? ""); } } public struct Event { public long SequenceNumber { get; set; } public string Name { get; set; } public object Content { get; set; } } public class Program : ServiceBase { private EventSubscriber subscriber; public static void Main(string[] args) => new Program().Main(); public void Main() { this.subscriber = new EventSubscriber("localhost:5000"); //Run(this); OnStart(null); ReadLine(); } protected override void OnStart(string[] args) { this.subscriber.Start(); } protected override void OnStop() { this.subscriber.Stop(); } } } ================================================ FILE: src/Chapter06/LoyaltyProgramEventConsumer/project.json ================================================ { "version": "1.0.0-*", "buildOptions": { "emitEntryPoint": true }, "dependencies": { "Newtonsoft.Json": "8.0.3", "System.ServiceProcess.ServiceController": "4.1.0", "System.Net.Http": "4.1.0" }, "frameworks": { "net461": { } }, "tooling": { "defaultNamespace": "LoyaltyProgramEventConsumer" } } ================================================ FILE: src/Chapter07/.idea.Ch7/riderModule.iml ================================================ ================================================ FILE: src/Chapter07/Ch7.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoyaltyProgram", "LoyaltyProgram\LoyaltyProgram.xproj", "{4BED3C45-E8C3-4345-A08F-249A39A256EB}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoyaltyProgramEventConsumer", "LoyaltyProgramEventConsumer\LoyaltyProgramEventConsumer.xproj", "{47349717-585A-43EE-96E8-7D5249CB431D}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoyaltyProgramUnitTests", "LoyaltyProgramUnitTests\LoyaltyProgramUnitTests.xproj", "{DF4D91A9-C2B2-47A1-B3F5-74D3A03E7AAF}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoyaltyProgramIntegrationTest", "LoyaltyProgramIntegrationTest\LoyaltyProgramIntegrationTest.xproj", "{4ADB7532-5BBB-4A6F-9044-6095427DB0E6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Release|Any CPU.Build.0 = Release|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {87C7C329-1824-4AC7-AF25-3D06068A01F6}.Release|Any CPU.Build.0 = Release|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Debug|Any CPU.Build.0 = Debug|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Release|Any CPU.ActiveCfg = Release|Any CPU {47349717-585A-43EE-96E8-7D5249CB431D}.Release|Any CPU.Build.0 = Release|Any CPU {DF4D91A9-C2B2-47A1-B3F5-74D3A03E7AAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DF4D91A9-C2B2-47A1-B3F5-74D3A03E7AAF}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF4D91A9-C2B2-47A1-B3F5-74D3A03E7AAF}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF4D91A9-C2B2-47A1-B3F5-74D3A03E7AAF}.Release|Any CPU.Build.0 = Release|Any CPU {4ADB7532-5BBB-4A6F-9044-6095427DB0E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4ADB7532-5BBB-4A6F-9044-6095427DB0E6}.Debug|Any CPU.Build.0 = Debug|Any CPU {4ADB7532-5BBB-4A6F-9044-6095427DB0E6}.Release|Any CPU.ActiveCfg = Release|Any CPU {4ADB7532-5BBB-4A6F-9044-6095427DB0E6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: src/Chapter07/LoyaltyProgram/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter07/LoyaltyProgram/Bootstrapper.cs ================================================ namespace LoyaltyProgram { using System; using Nancy; using Nancy.Bootstrapper; using Nancy.TinyIoc; public class Bootstrapper : DefaultNancyBootstrapper { protected override Func InternalConfiguration => NancyInternalConfiguration.WithOverrides(builder => builder.StatusCodeHandlers.Clear()); protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) { pipelines.OnError += (ctx, ex) => { // write to central log store return null; }; } } } ================================================ FILE: src/Chapter07/LoyaltyProgram/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter07/LoyaltyProgram/EventFeed/Event.cs ================================================ namespace LoyaltyProgram.EventFeed { using System; public struct Event { public long SequenceNumber { get; } public DateTimeOffset OccuredAt { get; } public string Name { get; } public object Content { get; } public Event( long sequenceNumber, DateTimeOffset occuredAt, string name, object content) { this.SequenceNumber = sequenceNumber; this.OccuredAt = occuredAt; this.Name = name; this.Content = content; } } } ================================================ FILE: src/Chapter07/LoyaltyProgram/EventFeed/EventStore.cs ================================================ namespace LoyaltyProgram.EventFeed { using System; using System.Collections.Generic; using System.Linq; using System.Threading; public class EventStore : IEventStore { private static long currentSequenceNumber = 0; private static readonly IList database = new List(); public IEnumerable GetEvents( long firstEventSequenceNumber, long lastEventSequenceNumber) => database .Where(e => e.SequenceNumber >= firstEventSequenceNumber && e.SequenceNumber <= lastEventSequenceNumber) .OrderBy(e => e.SequenceNumber); public void Raise(string eventName, object content) { var seqNumber = Interlocked.Increment(ref currentSequenceNumber); database.Add( new Event( seqNumber, DateTimeOffset.UtcNow, eventName, content)); } } } ================================================ FILE: src/Chapter07/LoyaltyProgram/EventFeed/EventsFeedModule.cs ================================================ namespace LoyaltyProgram.EventFeed { using Nancy; public class EventsFeedModule : NancyModule { public EventsFeedModule(IEventStore eventStore) : base("/events") { Get("/", _ => { long firstEventSequenceNumber, lastEventSequenceNumber; if (!long.TryParse(this.Request.Query.start.Value, out firstEventSequenceNumber)) firstEventSequenceNumber = 0; if (!long.TryParse(this.Request.Query.end.Value, out lastEventSequenceNumber)) lastEventSequenceNumber = 50; return eventStore.GetEvents( firstEventSequenceNumber, lastEventSequenceNumber); }); } } } ================================================ FILE: src/Chapter07/LoyaltyProgram/EventFeed/IEventStore.cs ================================================ using System.Collections.Generic; namespace LoyaltyProgram.EventFeed { public interface IEventStore { IEnumerable GetEvents(long firstEventSequenceNumber, long lastEventSequenceNumber); void Raise(string eventName, object content); } } ================================================ FILE: src/Chapter07/LoyaltyProgram/LoyaltyProgram.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 4bed3c45-e8c3-4345-a08f-249a39a256eb LoyaltyProgram .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter07/LoyaltyProgram/Program.cs ================================================ namespace LoyaltyProgram { using System.IO; using Microsoft.AspNetCore.Hosting; public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter07/LoyaltyProgram/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "LoyaltyProgram": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter07/LoyaltyProgram/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter07/LoyaltyProgram/Startup.cs ================================================ namespace LoyaltyProgram { using Microsoft.AspNetCore.Builder; using Nancy.Owin; public class Startup { public void Configure(IApplicationBuilder app) { app.UseOwin(buildFunc => buildFunc.UseNancy()); } } } ================================================ FILE: src/Chapter07/LoyaltyProgram/UsersModule.cs ================================================ using System.Collections.Generic; using Nancy; using Nancy.ModelBinding; namespace LoyaltyProgram { public class UsersModule : NancyModule { private static IDictionary registerUsers = new Dictionary(); public UsersModule() : base("/users") { Get("/", _ => registerUsers.Values); Get("/{userId:int}", parameters => { int userId = parameters.userId; if (registerUsers.ContainsKey(userId)) return registerUsers[userId]; else return HttpStatusCode.NotFound; }); Post("/", _ => { var newUser = this.Bind(); this.AddRegisteredUser(newUser); return this.CreatedResponse(newUser); }); Put("/{userId:int}", parameters => { int userId = parameters.userId; var updatedUser = this.Bind(); registerUsers[userId] = updatedUser; return updatedUser; }); } private dynamic CreatedResponse(LoyaltyProgramUser newUser) { return this.Negotiate .WithStatusCode(HttpStatusCode.Created) .WithHeader("Location", this.Request.Url.SiteBase + "/users/" + newUser.Id) .WithModel(newUser); } private void AddRegisteredUser(LoyaltyProgramUser newUser) { var userId = registerUsers.Count; newUser.Id = userId; registerUsers[userId] = newUser; } } public class LoyaltyProgramUser { public int Id { get; set; } public string Name { get; set; } public int LoyaltyPoints { get; set; } public LoyaltyProgramSettings Settings { get; set; } } public class LoyaltyProgramSettings { public string[] Interests { get; set; } } } ================================================ FILE: src/Chapter07/LoyaltyProgram/YamlSerializerDeserializer.cs ================================================ namespace LoyaltyProgram { using System; using System.Collections.Generic; using System.IO; using Nancy; using Nancy.ModelBinding; using Nancy.Responses.Negotiation; using YamlDotNet.Serialization; public class YamlBodyDeserializer : IBodyDeserializer { public bool CanDeserialize(MediaRange mediaRange, BindingContext context) => mediaRange.Subtype.ToString().EndsWith("yaml"); public object Deserialize(MediaRange mediaRange, Stream bodyStream, BindingContext context) { var yamlDeserializer = new Deserializer(); var reader = new StreamReader(bodyStream); return yamlDeserializer.Deserialize(reader, context.DestinationType); } } public class YamlBodySerializer : IResponseProcessor { public IEnumerable> ExtensionMappings { get { yield return new Tuple("yaml", new MediaRange("application/yaml")); } } public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context) => requestedMediaRange.Subtype.ToString().EndsWith("yaml") ? new ProcessorMatch { ModelResult = MatchResult.DontCare, RequestedContentTypeResult = MatchResult.NonExactMatch} : ProcessorMatch.None; public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context) => new Response { Contents = stream => { var yamlSerializer = new Serializer(); var streamWriter = new StreamWriter(stream); yamlSerializer.Serialize(streamWriter, model); streamWriter.Flush(); }, ContentType = "application/yaml" }; } } ================================================ FILE: src/Chapter07/LoyaltyProgram/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Owin": "1.0.0", "Nancy": "2.0.0-barneyrubble", "YamlDotNet": "3.8.0-pre233" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "LoyaltyProgram" } } ================================================ FILE: src/Chapter07/LoyaltyProgram/web.config ================================================ ================================================ FILE: src/Chapter07/LoyaltyProgramEventConsumer/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter07/LoyaltyProgramEventConsumer/LoyaltyProgramEventConsumer.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 47349717-585a-43ee-96e8-7d5249cb431d LoyaltyProgramEventConsumer .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter07/LoyaltyProgramEventConsumer/Program.cs ================================================ using static System.Console; namespace LoyaltyProgramEventConsumer { using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.ServiceProcess; using System.Threading.Tasks; using System.Timers; using Newtonsoft.Json; public class EventSubscriber { private readonly string loyaltyProgramHost; private readonly string notificationHost; private long start = 0, chunkSize = 100; private readonly Timer timer; public EventSubscriber(string loyaltyProgramHost) { WriteLine("created"); this.loyaltyProgramHost = loyaltyProgramHost; this.notificationHost = loyaltyProgramHost; this.timer = new Timer(10*1000); this.timer.AutoReset = false; this.timer.Elapsed += (_, __) => SubscriptionCycleCallback().Wait(); } private async Task SubscriptionCycleCallback() { var response = await ReadEvents(); if (response.StatusCode == HttpStatusCode.OK) await HandleEvents(await response.Content.ReadAsStringAsync()); this.timer.Start(); } private async Task ReadEvents() { using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri($"http://{this.loyaltyProgramHost}"); var response = await httpClient.GetAsync($"/events/?start={this.start}&end={this.start + this.chunkSize}").ConfigureAwait(false); PrettyPrintResponse(response); return response; } } private async Task HandleEvents(string content) { WriteLine("Handling events"); var events = JsonConvert.DeserializeObject>(content); WriteLine(events); WriteLine(events.Count()); var results = new List(); foreach (var ev in events) { WriteLine(ev.Content); dynamic eventData = ev.Content; WriteLine("product name from data: " + (string) eventData.item.productName); this.start = Math.Max(this.start, ev.SequenceNumber + 1); using (var httpClient = new HttpClient()) { WriteLine("notifying"); httpClient.BaseAddress = new Uri($"http://{this.notificationHost}"); var response = await httpClient.GetAsync("/notify").ConfigureAwait(false); PrettyPrintResponse(response); // results.Add(httpClient.GetAsync("/notify")); } } // await Task.WhenAll(results).ConfigureAwait(false); WriteLine("done notifying"); } public void Start() { this.timer.Start(); } public void Stop() { this.timer.Stop(); } private static async void PrettyPrintResponse(HttpResponseMessage response) { WriteLine("Status code: " + response?.StatusCode.ToString() ?? "command failed"); WriteLine("Headers: " + response?.Headers.Aggregate("", (acc, h) => acc + "\n\t" + h.Key + ": " + h.Value) ?? ""); WriteLine("Body: " + await response?.Content.ReadAsStringAsync() ?? ""); } } public struct Event { public long SequenceNumber { get; set; } public string Name { get; set; } public object Content { get; set; } } public class Program : ServiceBase { private EventSubscriber subscriber; public static void Main(string[] args) => new Program().Entry(args); public void Entry(string[] args) { this.subscriber = new EventSubscriber(args[0]); if (args.Length >= 2 && args[1].Equals("--service")) Run(this); else OnStart(null); ReadLine(); } protected override void OnStart(string[] args) { this.subscriber.Start(); } protected override void OnStop() { this.subscriber.Stop(); } } } ================================================ FILE: src/Chapter07/LoyaltyProgramEventConsumer/project.json ================================================ { "version": "1.0.0-*", "buildOptions": { "emitEntryPoint": true }, "dependencies": { "Newtonsoft.Json": "8.0.3", "System.ServiceProcess.ServiceController": "4.1.0-rc2-24027", "System.Net.Http": "4.1.0" }, "frameworks": { "net461": { } }, "tooling": { "defaultNamespace": "LoyaltyProgramEventConsumer" } } ================================================ FILE: src/Chapter07/LoyaltyProgramIntegrationTest/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter07/LoyaltyProgramIntegrationTest/FakeEndpoints.cs ================================================ namespace LoyaltyProgramIntegrationTests { using System; using System.Threading; using Microsoft.AspNetCore.Builder; using Nancy; using Nancy.Owin; public class FakeEventFeed : NancyModule { public static AutoResetEvent polled = new AutoResetEvent(initialState: false); public FakeEventFeed() { this.Get("/events", _ => { polled.Set(); return Response.AsJson(new [] { new { SequenceNumber = 1, Name= "baz", Content = new { OfferName = "foo", Desciption = "bar", item = new { ProductName = "name" }}}}); }); } } public class FakeNotifications : NancyModule { public static bool NotificationWasSent = false; public FakeNotifications() { this.Get("/notify", _ => { NotificationWasSent = true; Console.WriteLine("notified"); return 200; }); } } public class FakeStartup { public void Configure(IApplicationBuilder app) { app.UseOwin(buildFunc => buildFunc.UseNancy()); } } } ================================================ FILE: src/Chapter07/LoyaltyProgramIntegrationTest/LoyaltyProgramIntegrationTest.xproj ================================================  14.0.25123 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 4adb7532-5bbb-4a6f-9044-6095427db0e6 LoyaltyProgramIntegrationTest .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter07/LoyaltyProgramIntegrationTest/RegisterUserAndGetNotificationScenario.cs ================================================ namespace LoyaltyProgramIntegrationTests { using System; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Newtonsoft.Json; using Xunit; public class RegisterUserAndGetNotification : IDisposable { private IWebHost hostForFakeEndpoints; private Process eventConsumer; private Process api; private Thread thread; public RegisterUserAndGetNotification() { StartLoayaltyProgram(); StartFakeEndpoints(); } private void StartFakeEndpoints() { this.hostForFakeEndpoints = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup() .UseUrls("http://localhost:5001") .Build(); this.thread = new Thread(() => this.hostForFakeEndpoints.Run()); this.thread.Start(); } private void StartLoayaltyProgram() { StartEventConsumer(); StartLoyaltyProgramApi(); } private void StartLoyaltyProgramApi() { var apiInfo = new ProcessStartInfo("dotnet.exe") { Arguments = "run", WorkingDirectory = "../LoyaltyProgram" }; this.api = Process.Start(apiInfo); } private void StartEventConsumer() { var eventConsumerInfo = new ProcessStartInfo("dotnet.exe") { Arguments = "run localhost:5001", WorkingDirectory = "../LoyaltyProgramEventConsumer" }; this.eventConsumer = Process.Start(eventConsumerInfo); } [Fact] public void Scenario() { RegisterNewUser(); WaitForConsumerToReadSpeciallOffersEvents(); AssertNotificationWassent(); } private async Task RegisterNewUser() { using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri("http://localhost:5000"); var response = await httpClient.PostAsync( "/users/", new StringContent( JsonConvert.SerializeObject(new LoyaltyProgramUser()), Encoding.UTF8, "application/json")).ConfigureAwait(false); Assert.Equal(HttpStatusCode.Created, response.StatusCode); Console.WriteLine("registered users"); } } private static void AssertNotificationWassent() { Assert.True(FakeNotifications.NotificationWasSent); } private static void WaitForConsumerToReadSpeciallOffersEvents() { Console.WriteLine("waiting for event poll ...."); Assert.True(FakeEventFeed.polled.WaitOne(30000)); Console.WriteLine("got poll ...."); Thread.Sleep(1000); Console.WriteLine("waited for notification ...."); } public void Dispose() { Console.WriteLine("disposing..."); this.eventConsumer.Dispose(); this.api.Dispose(); //this.hostForFakeEndpoints.Dispose(); Console.WriteLine("disposed..."); } } public class LoyaltyProgramUser { public int Id { get; set; } public string Name { get; set; } public int LoyaltyPoints { get; set; } public LoyaltyProgramSettings Settings { get; set; } } public class LoyaltyProgramSettings { public string[] Interests { get; set; } } } ================================================ FILE: src/Chapter07/LoyaltyProgramIntegrationTest/project.json ================================================ { "version": "1.0.0-*", "buildOptions": { "preserveCompilationContext": true }, "dependencies": { "dotnet-test-xunit": "2.2.0-preview2-build1029", "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "xunit": "2.1.0", "Microsoft.AspNetCore.Owin": "1.0.0", "Nancy": "2.0.0-barneyrubble", "LoyaltyProgram": { "target": "project" } }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "testRunner": "xunit", "tooling": { "defaultNamespace": "LoyaltyProgramIntegrationTest" } } ================================================ FILE: src/Chapter07/LoyaltyProgramUnitTests/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter07/LoyaltyProgramUnitTests/EventFeed_should.cs ================================================ namespace LoyaltyProgramUnitTests { using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using LoyaltyProgram.EventFeed; using Nancy; using Nancy.Testing; using Xunit; public class EventFeed_should { private Browser sut; public EventFeed_should() { this.sut = new Browser( with => with.Module().Dependency(typeof (FakeEventStore)), withDefault => withDefault.Accept("application/json")); } [Fact] public async Task return_events_when_from_event_store() { var actual = await sut.Get("/events/", with => { with.Query("start", "0"); with.Query("end", "100"); }); Assert.Equal(HttpStatusCode.OK, actual.StatusCode); Assert.StartsWith("application/json", actual.ContentType); //Assert.Equal(100, actual.Body.DeserializeJson>().Count()); } [Fact] public async Task return_empty_response_when_there_are_no_more_events() { var actual = await sut.Get("/events/", with => { with.Query("start", "200"); with.Query("end", "300"); }); Assert.Empty(actual.Body.DeserializeJson>()); } } public class FakeEventStore : IEventStore { public IEnumerable GetEvents(long firstEventSequenceNumber, long lastEventSequenceNumber) { if (firstEventSequenceNumber > 100) return Enumerable.Empty(); else return Enumerable .Range((int) firstEventSequenceNumber, (int) (lastEventSequenceNumber - firstEventSequenceNumber)) .Select(i => new Event(i, DateTimeOffset.Now.AddMinutes(i), "some event", i)); } public void Raise(string eventName, object content) { } } } ================================================ FILE: src/Chapter07/LoyaltyProgramUnitTests/LoyaltyProgramUnitTests.xproj ================================================  14.0.25123 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) df4d91a9-c2b2-47a1-b3f5-74d3a03e7aaf LoyaltyProgramUnitTests .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter07/LoyaltyProgramUnitTests/TestModule_should.cs ================================================ namespace LoyaltyProgramUnitTests { using System.Threading.Tasks; using Nancy; using Nancy.Testing; using Xunit; public class TestModule_should { public class TestModule : NancyModule { public TestModule() { Get("/", _ => 200); } } [Fact] public async Task respond_ok_to_request_to_root() { var sut = new Browser(with => with.Module()); var actual = await sut.Get("/"); Assert.Equal(HttpStatusCode.OK, actual.StatusCode); } } } ================================================ FILE: src/Chapter07/LoyaltyProgramUnitTests/UserModule_should.cs ================================================ namespace LoyaltyProgramUnitTests { using System; using System.Threading.Tasks; using LoyaltyProgram; using Nancy; using Nancy.Testing; using Xunit; public class UserModule_should { private Browser sut; public UserModule_should() { this.sut = new Browser( new Bootstrapper(), defaultsTo => defaultsTo.Accept("application/json")); } [Fact] public async Task respond_not_when_queried_for_unregistered_user() { var actual = await sut.Get("/users/1000"); Assert.Equal(HttpStatusCode.NotFound, actual.StatusCode); } [Fact] public async Task allow_to_register_new_user() { var expected = new LoyaltyProgramUser() {Name = "Chr"}; var registrationResponse = await sut.Post("/users", with => with.JsonBody(expected)); var newUser = registrationResponse.Body.DeserializeJson(); var actual = await sut.Get($"/users/{newUser.Id}"); Assert.Equal(HttpStatusCode.OK, actual.StatusCode); Assert.Equal(expected.Name, actual.Body.DeserializeJson().Name); } [Fact] public async Task allow_modififying_users() { var expected = "jane"; var user = new LoyaltyProgramUser() {Name = "Chr"}; var registrationResponse = await sut.Post("/users", with => with.JsonBody(user)); var newUser = registrationResponse.Body.DeserializeJson(); newUser.Name = expected; var actual = await sut.Put($"/users/{newUser.Id}", with => with.JsonBody(newUser)); Assert.Equal(expected, actual.Body.DeserializeJson().Name); } } } ================================================ FILE: src/Chapter07/LoyaltyProgramUnitTests/project.json ================================================ { "version": "1.0.0-*", "buildOptions": { "preserveCompilationContext": true }, "dependencies": { "dotnet-test-xunit": "2.2.0-preview2-build1029", "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "xunit": "2.1.0", "Nancy.Testing": "2.0.0-barneyrubble", "LoyaltyProgram": {"target": "project"} }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "testRunner": "xunit", "tooling": { "defaultNamespace": "LoyaltyProgramUnitTests" } } ================================================ FILE: src/Chapter09/ShoppingCart/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter09/ShoppingCart/.vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "name": ".NET Core Launch (console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", "args": [], "cwd": "${workspaceRoot}", "stopAtEntry": false }, { "name": ".NET Core Launch (web)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", "args": [], "cwd": "${workspaceRoot}", "stopAtEntry": false, "launchBrowser": { "enabled": true, "args": "${auto-detect-url}", "windows": { "command": "cmd.exe", "args": "/C start ${auto-detect-url}" }, "osx": { "command": "open" }, "linux": { "command": "xdg-open" } } }, { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", "processName": "" } ] } ================================================ FILE: src/Chapter09/ShoppingCart/.vscode/tasks.json ================================================ { "version": "0.1.0", "command": "dotnet", "isShellCommand": true, "args": [], "tasks": [ { "taskName": "build", "args": [], "isBuildCommand": true, "problemMatcher": "$msCompile" } ] } ================================================ FILE: src/Chapter09/ShoppingCart/Application_Packages/LibOwin.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. // Copyright (c) Damian Hickey. All rights reserved. See License.txt in the project root for license information. // https://github.com/damianh/LibOwin // Modifying this file may result in difficulties when upgrading the package. // All types are internal. Add a LIBOWIN_PUBLIC compilation symbol to make them public. namespace LibOwin.Infrastructure { using System; using System.Collections; using System.Collections.Generic; using System.Linq; internal static partial class Constants { internal const string Https = "HTTPS"; internal const string HttpDateFormat = "r"; internal static partial class Headers { internal const string ContentType = "Content-Type"; internal const string CacheControl = "Cache-Control"; internal const string MediaType = "Media-Type"; internal const string Accept = "Accept"; internal const string Host = "Host"; internal const string ETag = "ETag"; internal const string Location = "Location"; internal const string ContentLength = "Content-Length"; internal const string SetCookie = "Set-Cookie"; internal const string Expires = "Expires"; } } internal struct HeaderSegment : IEquatable { private readonly StringSegment _formatting; private readonly StringSegment _data; // // Initializes a new instance of the class. // public HeaderSegment(StringSegment formatting, StringSegment data) { _formatting = formatting; _data = data; } public StringSegment Formatting { get { return _formatting; } } public StringSegment Data { get { return _data; } } #region Equality members public bool Equals(HeaderSegment other) { return _formatting.Equals(other._formatting) && _data.Equals(other._data); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegment && Equals((HeaderSegment)obj); } public override int GetHashCode() { unchecked { return (_formatting.GetHashCode() * 397) ^ _data.GetHashCode(); } } public static bool operator ==(HeaderSegment left, HeaderSegment right) { return left.Equals(right); } public static bool operator !=(HeaderSegment left, HeaderSegment right) { return !left.Equals(right); } #endregion } internal struct HeaderSegmentCollection : IEnumerable, IEquatable { private readonly string[] _headers; public HeaderSegmentCollection(string[] headers) { _headers = headers; } #region Equality members public bool Equals(HeaderSegmentCollection other) { return Equals(_headers, other._headers); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegmentCollection && Equals((HeaderSegmentCollection)obj); } public override int GetHashCode() { return (_headers != null ? _headers.GetHashCode() : 0); } public static bool operator ==(HeaderSegmentCollection left, HeaderSegmentCollection right) { return left.Equals(right); } public static bool operator !=(HeaderSegmentCollection left, HeaderSegmentCollection right) { return !left.Equals(right); } #endregion public Enumerator GetEnumerator() { return new Enumerator(_headers); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } internal struct Enumerator : IEnumerator { private readonly string[] _headers; private int _index; private string _header; private int _headerLength; private int _offset; private int _leadingStart; private int _leadingEnd; private int _valueStart; private int _valueEnd; private int _trailingStart; private Mode _mode; private static readonly string[] NoHeaders = new string[0]; public Enumerator(string[] headers) { _headers = headers ?? NoHeaders; _header = string.Empty; _headerLength = -1; _index = -1; _offset = -1; _leadingStart = -1; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; _mode = Mode.Leading; } private enum Mode { Leading, Value, ValueQuoted, Trailing, Produce, } private enum Attr { Value, Quote, Delimiter, Whitespace } public HeaderSegment Current { get { return new HeaderSegment( new StringSegment(_header, _leadingStart, _leadingEnd - _leadingStart), new StringSegment(_header, _valueStart, _valueEnd - _valueStart)); } } object IEnumerator.Current { get { return Current; } } public void Dispose() { } public bool MoveNext() { while (true) { if (_mode == Mode.Produce) { _leadingStart = _trailingStart; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; if (_offset == _headerLength && _leadingStart != -1 && _leadingStart != _offset) { // Also produce trailing whitespace _leadingEnd = _offset; return true; } _mode = Mode.Leading; } // if end of a string if (_offset == _headerLength) { ++_index; _offset = -1; _leadingStart = 0; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; // if that was the last string if (_index == _headers.Length) { // no more move nexts return false; } // grab the next string _header = _headers[_index] ?? string.Empty; _headerLength = _header.Length; } while (true) { ++_offset; char ch = _offset == _headerLength ? (char)0 : _header[_offset]; // todo - array of attrs Attr attr = char.IsWhiteSpace(ch) ? Attr.Whitespace : ch == '\"' ? Attr.Quote : (ch == ',' || ch == (char)0) ? Attr.Delimiter : Attr.Value; switch (_mode) { case Mode.Leading: switch (attr) { case Attr.Delimiter: _leadingEnd = _offset; _mode = Mode.Produce; break; case Attr.Quote: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.ValueQuoted; break; case Attr.Value: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; case Mode.Value: switch (attr) { case Attr.Quote: _mode = Mode.ValueQuoted; break; case Attr.Delimiter: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; break; case Attr.Value: // more break; case Attr.Whitespace: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Trailing; break; } break; case Mode.ValueQuoted: switch (attr) { case Attr.Quote: _mode = Mode.Value; break; case Attr.Delimiter: if (ch == (char)0) { _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; } break; case Attr.Value: case Attr.Whitespace: // more break; } break; case Mode.Trailing: switch (attr) { case Attr.Delimiter: _mode = Mode.Produce; break; case Attr.Quote: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.ValueQuoted; break; case Attr.Value: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; } if (_mode == Mode.Produce) { return true; } } } } public void Reset() { _index = 0; _offset = 0; _leadingStart = 0; _leadingEnd = 0; _valueStart = 0; _valueEnd = 0; } } } internal struct StringSegment : IEquatable { private readonly string _buffer; private readonly int _offset; private readonly int _count; // // Initializes a new instance of the class. // public StringSegment(string buffer, int offset, int count) { _buffer = buffer; _offset = offset; _count = count; } public string Buffer { get { return _buffer; } } public int Offset { get { return _offset; } } public int Count { get { return _count; } } public string Value { get { return _offset == -1 ? null : _buffer.Substring(_offset, _count); } } public bool HasValue { get { return _offset != -1 && _count != 0 && _buffer != null; } } #region Equality members public bool Equals(StringSegment other) { return string.Equals(_buffer, other._buffer) && _offset == other._offset && _count == other._count; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is StringSegment && Equals((StringSegment)obj); } public override int GetHashCode() { unchecked { int hashCode = (_buffer != null ? _buffer.GetHashCode() : 0); hashCode = (hashCode * 397) ^ _offset; hashCode = (hashCode * 397) ^ _count; return hashCode; } } public static bool operator ==(StringSegment left, StringSegment right) { return left.Equals(right); } public static bool operator !=(StringSegment left, StringSegment right) { return !left.Equals(right); } #endregion public bool StartsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public bool EndsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset + _count - textLength, text, 0, textLength, comparisonType) == 0; } public bool Equals(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count != textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public string Substring(int offset, int length) { return _buffer.Substring(_offset + offset, length); } public StringSegment Subsegment(int offset, int length) { return new StringSegment(_buffer, _offset + offset, length); } public override string ToString() { return Value ?? string.Empty; } } internal static partial class OwinHelpers { private static readonly Action AddCookieCallback = (name, value, state) => { var dictionary = (IDictionary)state; if (!dictionary.ContainsKey(name)) { dictionary.Add(name, value); } }; private static readonly char[] SemicolonAndComma = new[] { ';', ',' }; internal static IDictionary GetCookies(IOwinRequest request) { var cookies = request.Get>("Microsoft.Owin.Cookies#dictionary"); if (cookies == null) { cookies = new Dictionary(StringComparer.Ordinal); request.Set("Microsoft.Owin.Cookies#dictionary", cookies); } string text = GetHeader(request.Headers, "Cookie"); if (request.Get("Microsoft.Owin.Cookies#text") != text) { cookies.Clear(); ParseDelimited(text, SemicolonAndComma, AddCookieCallback, cookies); request.Set("Microsoft.Owin.Cookies#text", text); } return cookies; } internal static void ParseDelimited(string text, char[] delimiters, Action callback, object state) { int textLength = text.Length; int equalIndex = text.IndexOf('='); if (equalIndex == -1) { equalIndex = textLength; } int scanIndex = 0; while (scanIndex < textLength) { int delimiterIndex = text.IndexOfAny(delimiters, scanIndex); if (delimiterIndex == -1) { delimiterIndex = textLength; } if (equalIndex < delimiterIndex) { while (scanIndex != equalIndex && char.IsWhiteSpace(text[scanIndex])) { ++scanIndex; } string name = text.Substring(scanIndex, equalIndex - scanIndex); string value = text.Substring(equalIndex + 1, delimiterIndex - equalIndex - 1); callback( Uri.UnescapeDataString(name.Replace('+', ' ')), Uri.UnescapeDataString(value.Replace('+', ' ')), state); equalIndex = text.IndexOf('=', delimiterIndex); if (equalIndex == -1) { equalIndex = textLength; } } scanIndex = delimiterIndex + 1; } } } internal static partial class OwinHelpers { public static string GetHeader(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : string.Join(",", values); } public static IEnumerable GetHeaderSplit(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : GetHeaderSplitImplementation(values); } private static IEnumerable GetHeaderSplitImplementation(string[] values) { foreach (var segment in new HeaderSegmentCollection(values)) { if (segment.Data.HasValue) { yield return DeQuote(segment.Data.Value); } } } public static string[] GetHeaderUnmodified(IDictionary headers, string key) { if (headers == null) { throw new ArgumentNullException("headers"); } string[] values; return headers.TryGetValue(key, out values) ? values : null; } public static void SetHeader(IDictionary headers, string key, string value) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (string.IsNullOrWhiteSpace(value)) { headers.Remove(key); } else { headers[key] = new[] { value }; } } public static void SetHeaderJoined(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = new[] { string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } // Quote items that contain comas and are not already quoted. private static string QuoteIfNeeded(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Contains(',')) { if (value[0] != '"' || value[value.Length - 1] != '"') { value = '"' + value + '"'; } } return value; } private static string DeQuote(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"') { value = value.Substring(1, value.Length - 2); } return value; } public static void SetHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = values; } } public static void SetHeaderUnmodified(IDictionary headers, string key, IEnumerable values) { if (headers == null) { throw new ArgumentNullException("headers"); } headers[key] = values.ToArray(); } public static void AppendHeader(IDictionary headers, string key, string values) { if (string.IsNullOrWhiteSpace(values)) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeader(headers, key, values); } else { headers[key] = new[] { existing + "," + values }; } } public static void AppendHeaderJoined(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeaderJoined(headers, key, values); } else { headers[key] = new[] { existing + "," + string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } public static void AppendHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string[] existing = GetHeaderUnmodified(headers, key); if (existing == null) { SetHeaderUnmodified(headers, key, values); } else { SetHeaderUnmodified(headers, key, existing.Concat(values)); } } } internal static partial class OwinHelpers { private static readonly Action AppendItemCallback = (name, value, state) => { var dictionary = (IDictionary>)state; List existing; if (!dictionary.TryGetValue(name, out existing)) { dictionary.Add(name, new List(1) { value }); } else { existing.Add(value); } }; private static readonly char[] AmpersandAndSemicolon = new[] { '&', ';' }; internal static IDictionary GetQuery(IOwinRequest request) { var query = request.Get>("Microsoft.Owin.Query#dictionary"); if (query == null) { query = new Dictionary(StringComparer.OrdinalIgnoreCase); request.Set("Microsoft.Owin.Query#dictionary", query); } string text = request.QueryString.Value; if (request.Get("Microsoft.Owin.Query#text") != text) { query.Clear(); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, AmpersandAndSemicolon, AppendItemCallback, accumulator); foreach (var kv in accumulator) { query.Add(kv.Key, kv.Value.ToArray()); } request.Set("Microsoft.Owin.Query#text", text); } return query; } internal static IFormCollection GetForm(string text) { IDictionary form = new Dictionary(StringComparer.OrdinalIgnoreCase); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, new[] { '&' }, AppendItemCallback, accumulator); foreach (var kv in accumulator) { form.Add(kv.Key, kv.Value.ToArray()); } return new FormCollection(form); } internal static string GetJoinedValue(IDictionary store, string key) { string[] values = GetUnmodifiedValues(store, key); return values == null ? null : string.Join(",", values); } internal static string[] GetUnmodifiedValues(IDictionary store, string key) { if (store == null) { throw new ArgumentNullException("store"); } string[] values; return store.TryGetValue(key, out values) ? values : null; } } internal static partial class OwinHelpers { internal static string GetHost(IOwinRequest request) { IHeaderDictionary headers = request.Headers; string host = GetHeader(headers, "Host"); if (!string.IsNullOrWhiteSpace(host)) { return host; } string localIpAddress = request.LocalIpAddress ?? "localhost"; var localPort = request.Get(OwinConstants.CommonKeys.LocalPort); return string.IsNullOrWhiteSpace(localPort) ? localIpAddress : (localIpAddress + ":" + localPort); } } } namespace LibOwin { using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Text; using System.Threading; using System.Threading.Tasks; using LibOwin.Infrastructure; #if LIBOWIN_PUBLIC public partial class CookieOptions { } public partial class FormCollection { } public partial class HeaderDictionary { } public partial struct HostString { } public partial interface IFormCollection { } public partial interface IHeaderDictionary { } public partial interface IOwinContext { } public partial interface IOwinRequest { } public partial interface IOwinResponse { } public partial interface IReadableStringCollection { } public partial class OwinContext { } public partial class OwinRequest { } public partial class OwinResponse { } public partial struct PathString { } public partial struct QueryString { } public partial class ReadableStringCollection { } public partial class RequestCookieCollection { } public partial class ResponseCookieCollection { } public partial class IOwinResponseExtension { } #endif /// /// Options used to create a new cookie. /// partial class CookieOptions { /// /// Creates a default cookie with a path of '/'. /// public CookieOptions() { Path = "/"; } /// /// Gets or sets the domain to associate the cookie with. /// /// The domain to associate the cookie with. public string Domain { get; set; } /// /// Gets or sets the cookie path. /// /// The cookie path. public string Path { get; set; } /// /// Gets or sets the expiration date and time for the cookie. /// /// The expiration date and time for the cookie. public DateTime? Expires { get; set; } /// /// Gets or sets a value that indicates whether to transmit the cookie using Secure Sockets Layer (SSL)—that is, over HTTPS only. /// /// true to transmit the cookie only over an SSL connection (HTTPS); otherwise, false. public bool Secure { get; set; } /// /// Gets or sets a value that indicates whether a cookie is accessible by client-side script. /// /// true if a cookie is accessible by client-side script; otherwise, false. public bool HttpOnly { get; set; } } /// /// Contains the parsed form values. /// partial class FormCollection : ReadableStringCollection, IFormCollection { /// /// Initializes a new instance of the class. /// /// The store for the form. public FormCollection(IDictionary store) : base(store) {} } /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial class HeaderDictionary : IHeaderDictionary { /// /// Initializes a new instance of the class. /// /// The underlying data store. public HeaderDictionary(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Gets an that contains the keys in the ;. /// /// An that contains the keys in the . public ICollection Keys { get { return Store.Keys; } } /// /// /// public ICollection Values { get { return Store.Values; } } /// /// Gets the number of elements contained in the ;. /// /// The number of elements contained in the . public int Count { get { return Store.Count; } } /// /// Gets a value that indicates whether the is in read-only mode. /// /// true if the is in read-only mode; otherwise, false. public bool IsReadOnly { get { return Store.IsReadOnly; } } /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string this[string key] { get { return Get(key); } set { Set(key, value); } } /// /// Throws KeyNotFoundException if the key is not present. /// /// The header name. /// string[] IDictionary.this[string key] { get { return Store[key]; } set { Store[key] = value; } } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Get the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string Get(string key) { return OwinHelpers.GetHeader(Store, key); } /// /// Get the associated values from the collection without modification. /// /// The header name. /// the associated value from the collection without modification, or null if the key is not present. public IList GetValues(string key) { return OwinHelpers.GetHeaderUnmodified(Store, key); } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. public IList GetCommaSeparatedValues(string key) { IEnumerable values = OwinHelpers.GetHeaderSplit(Store, key); return values == null ? null : values.ToList(); } /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. public void Append(string key, string value) { OwinHelpers.AppendHeader(Store, key, value); } /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. public void AppendValues(string key, params string[] values) { OwinHelpers.AppendHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. public void AppendCommaSeparatedValues(string key, params string[] values) { OwinHelpers.AppendHeaderJoined(Store, key, values); } /// /// Sets a specific header value. /// /// The header name. /// The header value. public void Set(string key, string value) { OwinHelpers.SetHeader(Store, key, value); } /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. public void SetValues(string key, params string[] values) { OwinHelpers.SetHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. public void SetCommaSeparatedValues(string key, params string[] values) { OwinHelpers.SetHeaderJoined(Store, key, values); } /// /// Adds the given header and values to the collection. /// /// The header name. /// The header values. public void Add(string key, string[] value) { Store.Add(key, value); } /// /// Determines whether the contains a specific key. /// /// The key. /// true if the contains a specific key; otherwise, false. public bool ContainsKey(string key) { return Store.ContainsKey(key); } /// /// Removes the given header from the collection. /// /// The header name. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(string key) { return Store.Remove(key); } /// /// Retrieves a value from the dictionary. /// /// The header name. /// The value. /// true if the contains the key; otherwise, false. public bool TryGetValue(string key, out string[] value) { return Store.TryGetValue(key, out value); } /// /// Adds a new list of items to the collection. /// /// The item to add. public void Add(KeyValuePair item) { Store.Add(item); } /// /// Clears the entire list of objects. /// public void Clear() { Store.Clear(); } /// /// Returns a value indicating whether the specified object occurs within this collection. /// /// The item. /// true if the specified object occurs within this collection; otherwise, false. public bool Contains(KeyValuePair item) { return Store.Contains(item); } /// /// Copies the elements to a one-dimensional Array instance at the specified index. /// /// The one-dimensional Array that is the destination of the specified objects copied from the . /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) { Store.CopyTo(array, arrayIndex); } /// /// Removes the given item from the the collection. /// /// The item. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(KeyValuePair item) { return Store.Remove(item); } } /// /// Represents the host portion of a Uri can be used to construct Uri's properly formatted and encoded for use in /// HTTP headers. /// partial struct HostString : IEquatable { private readonly string _value; /// /// Creates a new HostString without modification. The value should be Unicode rather than punycode, and may have a port. /// IPv4 and IPv6 addresses are also allowed, and also may have ports. /// /// public HostString(string value) { _value = value; } /// /// Returns the original value from the constructor. /// public string Value { get { return _value; } } /// /// Returns the value as normalized by ToUriComponent(). /// /// public override string ToString() { return ToUriComponent(); } /// /// Returns the value properly formatted and encoded for use in a URI in a HTTP header. /// Any Unicode is converted to punycode. IPv6 addresses will have brackets added if they are missing. /// /// public string ToUriComponent() { int index; if (string.IsNullOrEmpty(_value)) { return string.Empty; } else if (_value.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port return _value; } else if ((index = _value.IndexOf(':')) >= 0 && index < _value.Length - 1 && _value.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons return "[" + _value + "]"; } else if (index >= 0) { // Has a port string port = _value.Substring(index); var mapping = new IdnMapping(); return mapping.GetAscii(_value, 0, index) + port; } else { var mapping = new IdnMapping(); return mapping.GetAscii(_value); } } /// /// Creates a new HostString from the given uri component. /// Any punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(string uriComponent) { if (!string.IsNullOrEmpty(uriComponent)) { int index; if (uriComponent.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port } else if ((index = uriComponent.IndexOf(':')) >= 0 && index < uriComponent.Length - 1 && uriComponent.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons } else if (uriComponent.IndexOf("xn--", StringComparison.Ordinal) >= 0) { // Contains punycode if (index >= 0) { // Has a port string port = uriComponent.Substring(index); IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent, 0, index) + port; } else { IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent); } } } return new HostString(uriComponent); } /// /// Creates a new HostString from the host and port of the give Uri instance. /// Punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new HostString(uri.GetComponents( UriComponents.NormalizedHost | // Always convert punycode to Unicode. UriComponents.HostAndPort, UriFormat.Unescaped)); } /// /// Compares the equality of the Value property, ignoring case. /// /// /// public bool Equals(HostString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares against the given object only if it is a HostString. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HostString && Equals((HostString)obj); } /// /// Gets a hash code for the value. /// /// public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(HostString left, HostString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(HostString left, HostString right) { return !left.Equals(right); } } /// /// Contains the parsed form values. /// partial interface IFormCollection : IReadableStringCollection {} /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial interface IHeaderDictionary : IReadableStringCollection, IDictionary { /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. new string this[string key] { get; set; } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. IList GetCommaSeparatedValues(string key); /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. void Append(string key, string value); /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. void AppendValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. void AppendCommaSeparatedValues(string key, params string[] values); /// /// Sets a specific header value. /// /// The header name. /// The header value. void Set(string key, string value); /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. void SetValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. void SetCommaSeparatedValues(string key, params string[] values); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinContext { /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. IOwinRequest Request { get; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. IOwinResponse Response { get; } /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. TextWriter TraceOutput { get; set; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinContext Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinRequest { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or set the HTTP method. /// /// The HTTP method. string Method { get; set; } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. string Scheme { get; set; } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. bool IsSecure { get; } /// /// Gets or set the Host header. May include the port. /// /// The Host header. HostString Host { get; set; } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. PathString PathBase { get; set; } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. PathString Path { get; set; } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. QueryString QueryString { get; set; } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. IReadableStringCollection Query { get; } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. Uri Uri { get; } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. string Protocol { get; set; } /// /// Gets the request headers. /// /// The request headers. IHeaderDictionary Headers { get; } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. RequestCookieCollection Cookies { get; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. string CacheControl { get; set; } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. string MediaType { get; set; } /// /// Gets or set the Accept header. /// /// The Accept header. string Accept { get; set; } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. Stream Body { get; set; } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. CancellationToken CallCancelled { get; set; } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. string LocalIpAddress { get; set; } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. int? LocalPort { get; set; } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. string RemoteIpAddress { get; set; } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. int? RemotePort { get; set; } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. ClaimsPrincipal User { get; set; } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. Task ReadFormAsync(); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinRequest Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinResponse { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. int StatusCode { get; set; } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. string ReasonPhrase { get; set; } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. string Protocol { get; set; } /// /// Gets the response header collection. /// /// The response header collection. IHeaderDictionary Headers { get; } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. ResponseCookieCollection Cookies { get; } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. long? ContentLength { get; set; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Expires header. /// /// The Expires header. DateTimeOffset? Expires { get; set; } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. string ETag { get; set; } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. Stream Body { get; set; } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. void OnSendingHeaders(Action callback, object state); /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. void Redirect(string location); /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. void Write(string text); /// /// Writes the given bytes to the response body stream. /// /// The response data. void Write(byte[] data); /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. void Write(byte[] data, int offset, int count); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(string text); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(string text, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, int offset, int count, CancellationToken token); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinResponse Set(string key, T value); } /// /// Accessors for headers, query, forms, etc. /// partial interface IReadableStringCollection : IEnumerable> { /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string this[string key] { get; } // Joined /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string Get(string key); // Joined /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// IList GetValues(string key); // Raw } internal static class OwinConstants { #region OWIN v1.0.0 - 3.2.1. Request Data // http://owin.org/spec/owin-1.0.0.html public const string RequestScheme = "owin.RequestScheme"; public const string RequestMethod = "owin.RequestMethod"; public const string RequestPathBase = "owin.RequestPathBase"; public const string RequestPath = "owin.RequestPath"; public const string RequestQueryString = "owin.RequestQueryString"; public const string RequestProtocol = "owin.RequestProtocol"; public const string RequestHeaders = "owin.RequestHeaders"; public const string RequestBody = "owin.RequestBody"; public const string RequestUser = "owin.RequestUser"; //owin 1.0.1 #endregion #region OWIN v1.0.0 - 3.2.2. Response Data // http://owin.org/spec/owin-1.0.0.html public const string ResponseStatusCode = "owin.ResponseStatusCode"; public const string ResponseReasonPhrase = "owin.ResponseReasonPhrase"; public const string ResponseProtocol = "owin.ResponseProtocol"; public const string ResponseHeaders = "owin.ResponseHeaders"; public const string ResponseBody = "owin.ResponseBody"; #endregion #region OWIN v1.0.0 - 3.2.3. Other Data // http://owin.org/spec/owin-1.0.0.html public const string CallCancelled = "owin.CallCancelled"; public const string OwinVersion = "owin.Version"; #endregion #region OWIN Keys for IAppBuilder.Properties internal static class Builder { public const string AddSignatureConversion = "builder.AddSignatureConversion"; public const string DefaultApp = "builder.DefaultApp"; } #endregion #region OWIN Key Guidelines and Common Keys - 6. Common keys // http://owin.org/spec/CommonKeys.html internal static class CommonKeys { public const string ClientCertificate = "ssl.ClientCertificate"; public const string RemoteIpAddress = "server.RemoteIpAddress"; public const string RemotePort = "server.RemotePort"; public const string LocalIpAddress = "server.LocalIpAddress"; public const string LocalPort = "server.LocalPort"; public const string IsLocal = "server.IsLocal"; public const string TraceOutput = "host.TraceOutput"; public const string Addresses = "host.Addresses"; public const string AppName = "host.AppName"; public const string Capabilities = "server.Capabilities"; public const string OnSendingHeaders = "server.OnSendingHeaders"; public const string OnAppDisposing = "host.OnAppDisposing"; public const string Scheme = "scheme"; public const string Host = "host"; public const string Port = "port"; public const string Path = "path"; } #endregion #region SendFiles v0.3.0 // http://owin.org/extensions/owin-SendFile-Extension-v0.3.0.htm internal static class SendFiles { // 3.1. Startup public const string Version = "sendfile.Version"; public const string Support = "sendfile.Support"; public const string Concurrency = "sendfile.Concurrency"; // 3.2. Per Request public const string SendAsync = "sendfile.SendAsync"; } #endregion #region Opaque v0.3.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class OpaqueConstants { // 3.1. Startup public const string Version = "opaque.Version"; // 3.2. Per Request public const string Upgrade = "opaque.Upgrade"; // 5. Consumption public const string Stream = "opaque.Stream"; // public const string Version = "opaque.Version"; // redundant, declared above public const string CallCancelled = "opaque.CallCancelled"; } #endregion #region WebSocket v0.4.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class WebSocket { // 3.1. Startup public const string Version = "websocket.Version"; // 3.2. Per Request public const string Accept = "websocket.Accept"; // 4. Accept public const string SubProtocol = "websocket.SubProtocol"; // 5. Consumption public const string SendAsync = "websocket.SendAsync"; public const string ReceiveAsync = "websocket.ReceiveAsync"; public const string CloseAsync = "websocket.CloseAsync"; // public const string Version = "websocket.Version"; // redundant, declared above public const string CallCancelled = "websocket.CallCancelled"; public const string ClientCloseStatus = "websocket.ClientCloseStatus"; public const string ClientCloseDescription = "websocket.ClientCloseDescription"; } #endregion #region Security v0.1.0 // http://owin.org/extensions/owin-Security-Extension-v0.1.0.htm internal static class Security { // 3.2. Per Request public const string User = "server.User"; public const string Authenticate = "security.Authenticate"; // 3.3. Response public const string SignIn = "security.SignIn"; public const string SignOut = "security.SignOut"; public const string SignOutProperties = "security.SignOutProperties"; public const string Challenge = "security.Challenge"; } #endregion } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinContext : IOwinContext { /// /// Create a new context with only request and response header collections. /// public OwinContext() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Create a new wrapper. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinContext(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. public virtual IOwinRequest Request { get; private set; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. public virtual IOwinResponse Response { get; private set; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. public virtual TextWriter TraceOutput { get { return Get(OwinConstants.CommonKeys.TraceOutput); } set { Set(OwinConstants.CommonKeys.TraceOutput, value); } } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinContext Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinRequest : IOwinRequest { /// /// Create a new context with only request and response header collections. /// public OwinRequest() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Create a new environment wrapper exposing request properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinRequest(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or set the HTTP method. /// /// The HTTP method. public virtual string Method { get { return Get(OwinConstants.RequestMethod); } set { Set(OwinConstants.RequestMethod, value); } } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. public virtual string Scheme { get { return Get(OwinConstants.RequestScheme); } set { Set(OwinConstants.RequestScheme, value); } } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. public virtual bool IsSecure { get { return string.Equals(Scheme, Constants.Https, StringComparison.OrdinalIgnoreCase); } } /// /// Gets or set the Host header. May include the port. /// /// The Host header. public virtual HostString Host { get { return new HostString(OwinHelpers.GetHost(this)); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Host, value.Value); } } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. public virtual PathString PathBase { get { return new PathString(Get(OwinConstants.RequestPathBase)); } set { Set(OwinConstants.RequestPathBase, value.Value); } } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. public virtual PathString Path { get { return new PathString(Get(OwinConstants.RequestPath)); } set { Set(OwinConstants.RequestPath, value.Value); } } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. public virtual QueryString QueryString { get { return new QueryString(Get(OwinConstants.RequestQueryString)); } set { Set(OwinConstants.RequestQueryString, value.Value); } } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. public virtual IReadableStringCollection Query { get { return new ReadableStringCollection(OwinHelpers.GetQuery(this)); } } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. public virtual Uri Uri { get { return new Uri(Scheme + "//" + Host + PathBase + Path + QueryString); } } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. public virtual string Protocol { get { return Get(OwinConstants.RequestProtocol); } set { Set(OwinConstants.RequestProtocol, value); } } /// /// Gets the request headers. /// /// The request headers. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.RequestHeaders); } } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. public RequestCookieCollection Cookies { get { return new RequestCookieCollection(OwinHelpers.GetCookies(this)); } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. public virtual string CacheControl { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.CacheControl); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.CacheControl, value); } } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. public virtual string MediaType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.MediaType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.MediaType, value); } } /// /// Gets or set the Accept header. /// /// The Accept header. public virtual string Accept { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Accept); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Accept, value); } } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. public virtual Stream Body { get { return Get(OwinConstants.RequestBody); } set { Set(OwinConstants.RequestBody, value); } } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. public virtual CancellationToken CallCancelled { get { return Get(OwinConstants.CallCancelled); } set { Set(OwinConstants.CallCancelled, value); } } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. public virtual string LocalIpAddress { get { return Get(OwinConstants.CommonKeys.LocalIpAddress); } set { Set(OwinConstants.CommonKeys.LocalIpAddress, value); } } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. public virtual int? LocalPort { get { int value; if (int.TryParse(LocalPortString, out value)) { return value; } return null; } set { if (value.HasValue) { LocalPortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.LocalPort); } } } private string LocalPortString { get { return Get(OwinConstants.CommonKeys.LocalPort); } set { Set(OwinConstants.CommonKeys.LocalPort, value); } } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. public virtual string RemoteIpAddress { get { return Get(OwinConstants.CommonKeys.RemoteIpAddress); } set { Set(OwinConstants.CommonKeys.RemoteIpAddress, value); } } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. public virtual int? RemotePort { get { int value; if (int.TryParse(RemotePortString, out value)) { return value; } return null; } set { if (value.HasValue) { RemotePortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.RemotePort); } } } private string RemotePortString { get { return Get(OwinConstants.CommonKeys.RemotePort); } set { Set(OwinConstants.CommonKeys.RemotePort, value); } } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. public virtual ClaimsPrincipal User { get { var claimsPrincipal = Get(OwinConstants.RequestUser); return claimsPrincipal ?? Get(OwinConstants.Security.User) as ClaimsPrincipal; } set { Set(OwinConstants.RequestUser, value); } } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. public async Task ReadFormAsync() { var form = Get("Microsoft.Owin.Form#collection"); if (form == null) { string text; // Don't close, it prevents re-winding. using (var reader = new StreamReader(Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 4 * 1024, leaveOpen: true)) { text = await reader.ReadToEndAsync(); } form = OwinHelpers.GetForm(text); Set("Microsoft.Owin.Form#collection", form); } return form; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinRequest Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinResponse : IOwinResponse { /// /// Create a new context with only request and response header collections. /// public OwinResponse() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Creates a new environment wrapper exposing response properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinResponse(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. public virtual int StatusCode { get { return Get(OwinConstants.ResponseStatusCode, 200); } set { Set(OwinConstants.ResponseStatusCode, value); } } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. public virtual string ReasonPhrase { get { return Get(OwinConstants.ResponseReasonPhrase); } set { Set(OwinConstants.ResponseReasonPhrase, value); } } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. public virtual string Protocol { get { return Get(OwinConstants.ResponseProtocol); } set { Set(OwinConstants.ResponseProtocol, value); } } /// /// Gets the response header collection. /// /// The response header collection. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.ResponseHeaders); } } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. public virtual ResponseCookieCollection Cookies { get { return new ResponseCookieCollection(Headers); } } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. public virtual long? ContentLength { get { long value; if (long.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentLength), out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentLength, value.Value.ToString(CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.ContentLength); } } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Expires header. /// /// The Expires header. public virtual DateTimeOffset? Expires { get { DateTimeOffset value; if (DateTimeOffset.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Expires), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Expires, value.Value.ToString(Constants.HttpDateFormat, CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.Expires); } } } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. public virtual string ETag { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ETag); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ETag, value); } } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. public virtual Stream Body { get { return Get(OwinConstants.ResponseBody); } set { Set(OwinConstants.ResponseBody, value); } } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. public virtual void OnSendingHeaders(Action callback, object state) { var onSendingHeaders = Get, object>>(OwinConstants.CommonKeys.OnSendingHeaders); if (onSendingHeaders == null) { throw new NotSupportedException(Resources.Exception_MissingOnSendingHeaders); } onSendingHeaders(callback, state); } /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. public virtual void Redirect(string location) { StatusCode = 302; OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Location, location); } /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. public virtual void Write(string text) { Write(Encoding.UTF8.GetBytes(text)); } /// /// Writes the given bytes to the response body stream. /// /// The response data. public virtual void Write(byte[] data) { Write(data, 0, data == null ? 0 : data.Length); } /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. public virtual void Write(byte[] data, int offset, int count) { Body.Write(data, offset, count); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text) { return WriteAsync(text, CancellationToken.None); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text, CancellationToken token) { return WriteAsync(Encoding.UTF8.GetBytes(text), token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data) { return WriteAsync(data, CancellationToken.None); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, CancellationToken token) { return WriteAsync(data, 0, data == null ? 0 : data.Length, token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, int offset, int count, CancellationToken token) { return Body.WriteAsync(data, offset, count, token); } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { return Get(key, default(T)); } private T Get(string key, T fallback) { object value; return Environment.TryGetValue(key, out value) ? (T)value : fallback; } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinResponse Set(string key, T value) { Environment[key] = value; return this; } } /// /// Provides correct escaping for Path and PathBase values when needed to reconstruct a request or redirect URI string /// partial struct PathString : IEquatable { private static readonly Func EscapeDataString = Uri.EscapeDataString; /// /// Represents the empty path. This field is read-only. /// public static readonly PathString Empty = new PathString(String.Empty); private readonly string _value; /// /// Initialize the path string with a given value. This value must be in un-escaped format. Use /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format. /// /// The unescaped path to be assigned to the Value property. public PathString(string value) { if (!String.IsNullOrEmpty(value) && value[0] != '/') { throw new ArgumentException(Resources.Exception_PathMustStartWithSlash, "value"); } _value = value; } /// /// The unescaped path value /// public string Value { get { return _value; } } /// /// True if the path is not empty /// public bool HasValue { get { return !String.IsNullOrEmpty(_value); } } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public override string ToString() { return ToUriComponent(); } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public string ToUriComponent() { if (HasValue) { if (RequiresEscaping(_value)) { // TODO: Measure the cost of this escaping and consider optimizing. return String.Join("/", _value.Split('/').Select(EscapeDataString)); } return _value; } return String.Empty; } // Very conservative, these characters do not need to be escaped in a path. private static bool RequiresEscaping(string value) { for (int i = 0; i < value.Length; i++) { char c = value[i]; // Check conservatively for safe characters. See http://www.ietf.org/rfc/rfc3986.txt bool safeChar = (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '/' || c == '-' || c == '_'); if (!safeChar) { return true; } } return false; } /// /// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a path. /// /// The escaped path as it appears in the URI format. /// The resulting PathString public static PathString FromUriComponent(string uriComponent) { // REVIEW: what is the exactly correct thing to do? return new PathString(Uri.UnescapeDataString(uriComponent)); } /// /// Returns an PathString given the path as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting PathString public static PathString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } // REVIEW: what is the exactly correct thing to do? return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)); } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// public bool StartsWithSegments(PathString other) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { return value1.Length == value2.Length || value1[value2.Length] == '/'; } return false; } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// Any remaining segments from this instance not included in the other instance. /// public bool StartsWithSegments(PathString other, out PathString remaining) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { if (value1.Length == value2.Length || value1[value2.Length] == '/') { remaining = new PathString(value1.Substring(value2.Length)); return true; } } remaining = Empty; return false; } /// /// Adds two PathString instances into a combined PathString value. /// /// The combined PathString value public PathString Add(PathString other) { return new PathString(Value + other.Value); } /// /// Combines a PathString and QueryString into the joined URI formatted string value. /// /// The joined URI formatted string value public string Add(QueryString other) { return ToUriComponent() + other.ToUriComponent(); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public bool Equals(PathString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares this PathString value to another value using a specific StringComparison type /// /// The second PathString for comparison /// The StringComparison type to use /// True if both PathString values are equal public bool Equals(PathString other, StringComparison comparisonType) { return string.Equals(_value, other._value, comparisonType); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is PathString && Equals((PathString)obj, StringComparison.OrdinalIgnoreCase); } /// /// Returns the hash code for the PathString value. The hash code is provided by the OrdinalIgnoreCase implementation. /// /// The hash code public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are equal public static bool operator ==(PathString left, PathString right) { return left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are not equal public static bool operator !=(PathString left, PathString right) { return !left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static PathString operator +(PathString left, PathString right) { return left.Add(right); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static string operator +(PathString left, QueryString right) { return left.Add(right); } } /// /// Provides correct handling for QueryString value when needed to reconstruct a request or redirect URI string /// partial struct QueryString : IEquatable { /// /// Represents the empty query string. This field is read-only. /// public static readonly QueryString Empty = new QueryString(String.Empty); private readonly string _value; /// /// Initalize the query string with a given value. This value must be in escaped and delimited format without /// a leading '?' character. /// /// The query string to be assigned to the Value property. public QueryString(string value) { _value = value; } /// /// Initialize a query string with a single given parameter name and value. The value is /// /// The unencoded parameter name /// The unencoded parameter value public QueryString(string name, string value) { _value = Uri.EscapeDataString(name) + '=' + Uri.EscapeDataString(value); } /// /// The unescaped query string without the leading '?' character /// public string Value { get { return _value; } } /// /// True if the query string is not empty /// public bool HasValue { get { return !String.IsNullOrWhiteSpace(_value); } } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentally /// dangerous are escaped. /// /// The query string value public override string ToString() { return ToUriComponent(); } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentially /// dangerous are escaped. /// /// The query string value public string ToUriComponent() { // Escape things properly so System.Uri doesn't mis-interpret the data. return HasValue ? "?" + _value.Replace("#", "%23") : String.Empty; } /// /// Returns an QueryString given the query as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a query. /// /// The escaped query as it appears in the URI format. /// The resulting QueryString public static QueryString FromUriComponent(string uriComponent) { if (String.IsNullOrEmpty(uriComponent)) { return new QueryString(string.Empty); } if (uriComponent[0] != '?') { throw new ArgumentException(Resources.Exception_QueryStringMustStartWithDelimiter, "uriComponent"); } return new QueryString(uriComponent.Substring(1)); } /// /// Returns an QueryString given the query as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting QueryString public static QueryString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new QueryString(uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped)); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public bool Equals(QueryString other) { return string.Equals(_value, other._value); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is QueryString && Equals((QueryString)obj); } /// /// Returns the hash code for this instance. /// /// public override int GetHashCode() { return (_value != null ? _value.GetHashCode() : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(QueryString left, QueryString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(QueryString left, QueryString right) { return !left.Equals(right); } } /// /// Accessors for query, forms, etc. /// partial class ReadableStringCollection : IReadableStringCollection { /// /// Create a new wrapper /// /// public ReadableStringCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string this[string key] { get { return Get(key); } } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string Get(string key) { return OwinHelpers.GetJoinedValue(Store, key); } /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// public IList GetValues(string key) { string[] values; Store.TryGetValue(key, out values); return values; } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } /// /// A wrapper for the request Cookie header /// partial class RequestCookieCollection : IEnumerable> { /// /// Create a new wrapper /// /// public RequestCookieCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Returns null rather than throwing KeyNotFoundException /// /// /// public string this[string key] { get { string value; Store.TryGetValue(key, out value); return value; } } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } internal static class Resources { internal const string Exception_MissingOnSendingHeaders = "The OWIN key 'server.OnSsoendingHeaders' is not available for this request."; internal const string Exception_PathMustStartWithSlash = "The path must start with a '/' followed by one or more characters."; internal const string Exception_QueryStringMustStartWithDelimiter = "The query string must start with a '?' unless null or empty."; } /// /// A wrapper for the response Set-Cookie header /// partial class ResponseCookieCollection { /// /// Create a new wrapper /// /// public ResponseCookieCollection(IHeaderDictionary headers) { if (headers == null) { throw new ArgumentNullException("headers"); } Headers = headers; } private IHeaderDictionary Headers { get; set; } /// /// Add a new cookie and value /// /// /// public void Append(string key, string value) { Headers.AppendValues(Constants.Headers.SetCookie, Uri.EscapeDataString(key) + "=" + Uri.EscapeDataString(value) + "; path=/"); } /// /// Add a new cookie /// /// /// /// public void Append(string key, string value, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); bool expiresHasValue = options.Expires.HasValue; string setCookieValue = string.Concat( Uri.EscapeDataString(key), "=", Uri.EscapeDataString(value ?? string.Empty), !domainHasValue ? null : "; domain=", !domainHasValue ? null : options.Domain, !pathHasValue ? null : "; path=", !pathHasValue ? null : options.Path, !expiresHasValue ? null : "; expires=", !expiresHasValue ? null : options.Expires.Value.ToString("ddd, dd-MMM-yyyy HH:mm:ss ", CultureInfo.InvariantCulture) + "GMT", !options.Secure ? null : "; secure", !options.HttpOnly ? null : "; HttpOnly"); Headers.AppendValues("Set-Cookie", setCookieValue); } /// /// Sets an expired cookie /// /// public void Delete(string key) { Func predicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); var deleteCookies = new[] { Uri.EscapeDataString(key) + "=; expires=Thu, 01-Jan-1970 00:00:00 GMT" }; IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues == null || existingValues.Count == 0) { Headers.SetValues(Constants.Headers.SetCookie, deleteCookies); } else { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !predicate(value)).Concat(deleteCookies).ToArray()); } } /// /// Sets an expired cookie /// /// /// public void Delete(string key, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); Func rejectPredicate; if (domainHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("domain=" + options.Domain, StringComparison.OrdinalIgnoreCase) != -1; } else if (pathHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("path=" + options.Path, StringComparison.OrdinalIgnoreCase) != -1; } else { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); } IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues != null) { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !rejectPredicate(value)).ToArray()); } Append(key, string.Empty, new CookieOptions { Path = options.Path, Domain = options.Domain, Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), }); } } static partial class IOwinResponseExtension { /// /// Registers for an event that fires when the response headers are sent. /// /// The owin response /// The callback method. /// The callback state. public static void OnSendingHeaders(this IOwinResponse response, Action callback, T state) { if (response == null) { throw new ArgumentNullException("response"); } Action innerCallback = innerState => callback((T)innerState); response.OnSendingHeaders(innerCallback, state); } } } ================================================ FILE: src/Chapter09/ShoppingCart/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter09/ShoppingCart/EventFeed/Event.cs ================================================ namespace ShoppingCart.EventFeed { using System; public struct Event { public long SequenceNumber { get; } public DateTimeOffset OccuredAt { get; } public string Name { get; } public object Content { get; } public Event( long sequenceNumber, DateTimeOffset occuredAt, string name, object content) { this.SequenceNumber = sequenceNumber; this.OccuredAt = occuredAt; this.Name = name; this.Content = content; } } } ================================================ FILE: src/Chapter09/ShoppingCart/EventFeed/EventStore.cs ================================================ namespace ShoppingCart.EventFeed { using System; using System.Collections.Generic; using System.Linq; using System.Threading; #if net461 using global::EventStore.ClientAPI; #endif public class EventStore : IEventStore { private static long currentSequenceNumber = 0; private static readonly IList database = new List(); #if net461 private const string connectionString = "ConnectTo=discover://admin:changeit@127.0.0.1:2112/"; private IEventStoreConnection connection = EventStoreConnection.Create(connectionString); #endif public IEnumerable GetEvents( long firstEventSequenceNumber, long lastEventSequenceNumber) => database .Where(e => e.SequenceNumber >= firstEventSequenceNumber && e.SequenceNumber <= lastEventSequenceNumber) .OrderBy(e => e.SequenceNumber); public void Raise(string eventName, object content) { var seqNumber = Interlocked.Increment(ref currentSequenceNumber); database.Add( new Event( seqNumber, DateTimeOffset.UtcNow, eventName, content)); } } } ================================================ FILE: src/Chapter09/ShoppingCart/EventFeed/EventsFeedModule.cs ================================================ namespace ShoppingCart.EventFeed { using Nancy; public class EventsFeedModule : NancyModule { public EventsFeedModule(IEventStore eventStore) : base("/events") { Get("/", _ => { long firstEventSequenceNumber, lastEventSequenceNumber; if (!long.TryParse(this.Request.Query.start.Value, out firstEventSequenceNumber)) firstEventSequenceNumber = 0; if (!long.TryParse(this.Request.Query.end.Value, out lastEventSequenceNumber)) lastEventSequenceNumber = long.MaxValue; return eventStore.GetEvents( firstEventSequenceNumber, lastEventSequenceNumber); }); } } } ================================================ FILE: src/Chapter09/ShoppingCart/EventFeed/IEventStore.cs ================================================ using System.Collections.Generic; using ShoppingCart.ShoppingCart; namespace ShoppingCart.EventFeed { public interface IEventStore { IEnumerable GetEvents(long firstEventSequenceNumber, long lastEventSequenceNumber); void Raise(string eventName, object content); } } ================================================ FILE: src/Chapter09/ShoppingCart/ICache.cs ================================================ namespace ShoppingCart { using System; using System.Collections.Concurrent; using System.Collections.Generic; public interface ICache { void Add(string key, object value, TimeSpan ttl); object Get(string productsResource); } public class Cache : ICache { private static IDictionary> cache = new ConcurrentDictionary>(); public void Add(string key, object value, TimeSpan ttl) { cache[key] = Tuple.Create(DateTimeOffset.UtcNow.Add(ttl), value); } public object Get(string productsResource) { Tuple value; if (cache.TryGetValue(productsResource, out value) && value.Item1 > DateTimeOffset.UtcNow) return value; cache.Remove(productsResource); return null; } } } ================================================ FILE: src/Chapter09/ShoppingCart/IProductCatalogueClient.cs ================================================ namespace ShoppingCart { using System.Collections.Generic; using System.Threading.Tasks; using ShoppingCart; public interface IProductCatalogueClient { Task> GetShoppingCartItems(int[] productCatalogueIds); } } ================================================ FILE: src/Chapter09/ShoppingCart/LoggingMiddleware.cs ================================================ namespace ShoppingCart.Infrastructure { using System; using System.Diagnostics; using LibOwin; using Serilog; using Serilog.Context; using AppFunc = System.Func, System.Threading.Tasks.Task>; public class RequestLogging { public static AppFunc Middleware(AppFunc next, ILogger log) { return async env => { var owinContext = new OwinContext(env); log.Information( "Incoming request: {@Method}, {@Path}, {@Headers}", owinContext.Request.Method, owinContext.Request.Path, owinContext.Request.Headers); await next(env); log.Information( "Outgoing response: {@StatucCode}, {@Headers}", owinContext.Response.StatusCode, owinContext.Response.Headers); }; } } public class PerformanceLogging { public static AppFunc Middleware(AppFunc next, ILogger log) { return async env => { var stopWatch = new Stopwatch(); stopWatch.Start(); await next(env); stopWatch.Stop(); var owinContext = new OwinContext(env); log.Information( "Request: {@Method} {@Path} executed in {RequestTime:000} ms", owinContext.Request.Method, owinContext.Request.Path, stopWatch.ElapsedMilliseconds); }; } } public class CorrelationToken { public static AppFunc Middleware(AppFunc next) { return async env => { Guid correlationToken; var owinContext = new OwinContext(env); if (!(owinContext.Request.Headers["Correlation-Token"] != null && Guid.TryParse(owinContext.Request.Headers["Correlation-Token"], out correlationToken))) correlationToken = Guid.NewGuid(); owinContext.Set("correlationToken", correlationToken.ToString()); using (LogContext.PushProperty("CorrelationToken", correlationToken)) await next(env); }; } } public class GlobalErrorLogging { public static AppFunc Middleware(AppFunc next, ILogger log) { return async env => { try { await next(env); } catch (Exception ex) { log.Error(ex, "Unhandled exception"); } }; } } } ================================================ FILE: src/Chapter09/ShoppingCart/MonitoringMiddleware.cs ================================================ namespace ShoppingCart.Infrastructure { using System; using System.Collections.Generic; using System.Threading.Tasks; using LibOwin; using AppFunc = System.Func, System.Threading.Tasks.Task>; public class MonitoringMiddleware { private AppFunc next; private Func> healthCheck; private static readonly PathString monitorPath = new PathString("/_monitor"); private static readonly PathString monitorShallowPath = new PathString("/_monitor/shallow"); private static readonly PathString monitorDeepPath = new PathString("/_monitor/deep"); public MonitoringMiddleware(AppFunc next, Func> healthCheck) { this.next = next; this.healthCheck = healthCheck; } public Task Invoke(IDictionary env) { var context = new OwinContext(env); if (context.Request.Path.StartsWithSegments(monitorPath)) return HandleMonitorEndpoint(context); else return this.next(env); } private Task HandleMonitorEndpoint(OwinContext context) { if (context.Request.Path.StartsWithSegments(monitorShallowPath)) return ShallowEndpoint(context); else if (context.Request.Path.StartsWithSegments(monitorDeepPath)) return DeepEndpoint(context); return Task.FromResult(0); } private async Task DeepEndpoint(OwinContext context) { if (await this.healthCheck()) context.Response.StatusCode = 204; else context.Response.StatusCode = 503; } private Task ShallowEndpoint(OwinContext context) { context.Response.StatusCode = 204; return Task.FromResult(0); } } } ================================================ FILE: src/Chapter09/ShoppingCart/ProductCatalogueClient.cs ================================================ namespace ShoppingCart { using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Net.Http; using System.Threading; using Newtonsoft.Json; using Polly; using ShoppingCart; public class ProductCatalogueClient : IProductCatalogueClient { private static Policy exponentialRetryPolicy = Policy .Handle() .WaitAndRetryAsync( 3, attempt => TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt)), (ex, _) => Console.WriteLine(ex.ToString())); private static string productCatalogueBaseUrl = @"http://private-05cc8-chapter2productcataloguemicroservice.apiary-mock.com"; private static string getProductPathTemplate = "/products?productIds=[{0}]"; public Task> GetShoppingCartItems(int[] productCatalogueIds) => exponentialRetryPolicy .ExecuteAsync(() => GetItemsFromCatalogueService(productCatalogueIds)); private async Task> GetItemsFromCatalogueService(int[] productCatalogueIds) { var response = await RequestProductFromProductCatalogue(productCatalogueIds).ConfigureAwait(false); return await ConvertToShoppingCartItems(response).ConfigureAwait(false); } private static async Task RequestProductFromProductCatalogue(int[] productCatalogueIds) { var productsResource = string.Format( getProductPathTemplate, string.Join(",", productCatalogueIds)); using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri(productCatalogueBaseUrl); return await httpClient.GetAsync(productsResource).ConfigureAwait(false); } } private static async Task> ConvertToShoppingCartItems(HttpResponseMessage response) { response.EnsureSuccessStatusCode(); var products = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync().ConfigureAwait(false)); return products .Select(p => new ShoppingCartItem( int.Parse(p.ProductId), p.ProductName, p.ProductDescription, p.Price )); } private class ProductCatalogueProduct { public string ProductId { get; set; } public string ProductName { get; set; } public string ProductDescription { get; set; } public Money Price { get; set; } } } } ================================================ FILE: src/Chapter09/ShoppingCart/Program.cs ================================================ using System.IO; using Microsoft.AspNetCore.Hosting; namespace ShoppingCart { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter09/ShoppingCart/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "ShoppingCart": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter09/ShoppingCart/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter09/ShoppingCart/ShoppingCart/IShoppingCartStore.cs ================================================ namespace ShoppingCart.ShoppingCart { public interface IShoppingCartStore { ShoppingCart Get(int userId); void Save(ShoppingCart shoppingCart); } } ================================================ FILE: src/Chapter09/ShoppingCart/ShoppingCart/ShoppingCart.cs ================================================ namespace ShoppingCart.ShoppingCart { using System.Collections.Generic; using System.Linq; using global::ShoppingCart.EventFeed; public class ShoppingCart { private HashSet items = new HashSet(); public int UserId { get; } public IEnumerable Items { get { return items; } } public ShoppingCart(int userId) { this.UserId = userId; } public void AddItems( IEnumerable shoppingCartItems, IEventStore eventStore) { foreach (var item in shoppingCartItems) if (this.items.Add(item)) eventStore.Raise( "ShoppingCartItemAdded", new { UserId, item }); } public void RemoveItems( int[] productCatalogueIds, IEventStore eventStore) { items.RemoveWhere(i => productCatalogueIds.Contains(i.ProductCatalogueId)); } } public class ShoppingCartItem { public int ProductCatalogueId { get; } public string ProductName { get; } public string Desscription { get; } public Money Price { get; } public ShoppingCartItem( int productCatalogueId, string productName, string description, Money price) { this.ProductCatalogueId = productCatalogueId; this.ProductName = productName; this.Desscription = description; this.Price = price; } public override bool Equals(object obj) { if (obj == null || GetType() != obj.GetType()) { return false; } var that = obj as ShoppingCartItem; return this.ProductCatalogueId.Equals(that.ProductCatalogueId); } // override object.GetHashCode public override int GetHashCode() { return this.ProductCatalogueId.GetHashCode(); } } public class Money { public string Currency { get; } public decimal Amount { get; } public Money(string currency, decimal amount) { this.Currency = currency; this.Amount = amount; } } } ================================================ FILE: src/Chapter09/ShoppingCart/ShoppingCart/ShoppingCartModule.cs ================================================ namespace ShoppingCart.ShoppingCart { using EventFeed; using Nancy; using Nancy.ModelBinding; public class ShoppingCartModule : NancyModule { public ShoppingCartModule( IShoppingCartStore shoppingCartStore, IProductCatalogueClient productCatalogue, IEventStore eventStore) : base("/shoppingcart") { Get("/{userid:int}", parameters => { var userId = (int) parameters.userid; return shoppingCartStore.Get(userId); }); Post("/{userid:int}/items", async (parameters, _) => { var productCatalogueIds = this.Bind(); var userId = (int) parameters.userid; var shoppingCart = shoppingCartStore.Get(userId); var shoppingCartItems = await productCatalogue.GetShoppingCartItems(productCatalogueIds).ConfigureAwait(false); shoppingCart.AddItems(shoppingCartItems, eventStore); shoppingCartStore.Save(shoppingCart); return shoppingCart; }); Delete("/{userid:int}/items", parameters => { var productCatalogueIds = this.Bind(); var userId = (int)parameters.userid; var shoppingCart = shoppingCartStore.Get(userId); shoppingCart.RemoveItems(productCatalogueIds, eventStore); shoppingCartStore.Save(shoppingCart); return shoppingCart; }); } } } ================================================ FILE: src/Chapter09/ShoppingCart/ShoppingCart/ShoppingCartStore.cs ================================================ namespace ShoppingCart.ShoppingCart { using System.Collections.Generic; public class ShoppingCartStore : IShoppingCartStore { private static readonly Dictionary database = new Dictionary(); public ShoppingCart Get(int userId) { if (!database.ContainsKey(userId)) database[userId] = new ShoppingCart(userId); return database[userId]; } public void Save(ShoppingCart shoppingCart) { // Nothing needed. Saving would be needed with a real DB } } } ================================================ FILE: src/Chapter09/ShoppingCart/ShoppingCart.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 10e4f601-c16b-4936-a7b4-d32d799318d1 ShoppingCart .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter09/ShoppingCart/Startup.cs ================================================ namespace ShoppingCart { using System; using System.Data.SqlClient; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using System.Linq; using Dapper; using Nancy.Owin; using Serilog; using global::ShoppingCart.Infrastructure; using Nancy; using Nancy.Bootstrapper; using Nancy.TinyIoc; using System.Net.Http; using Serilog.Events; public interface IHttpClientFactory { HttpClient Create(Uri uri); } public class HttpClientFactory : IHttpClientFactory { private readonly string correlationToken; public HttpClientFactory(string correlationToken) { this.correlationToken = correlationToken; } public HttpClient Create(Uri uri) { var client = new HttpClient() { BaseAddress = uri } ; client.DefaultRequestHeaders.Add("Correlation-Token", this.correlationToken); return client; } } public class Bootstrapper : DefaultNancyBootstrapper { private readonly ILogger log; public Bootstrapper(ILogger log) { this.log = log; } protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) { base.ApplicationStartup(container, pipelines); container.Register(this.log); } protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context) { base.RequestStartup(container, pipelines, context); var correlationToken = context.GetOwinEnvironment()["correlationToken"] as string; container.Register(new HttpClientFactory(correlationToken)); } } public class Startup { public void Configure(IApplicationBuilder app) { var log = ConfigureLogger(); app.UseOwin(buildFunc => { buildFunc(next => GlobalErrorLogging.Middleware(next, log)); buildFunc(next => CorrelationToken.Middleware(next)); buildFunc(next => RequestLogging.Middleware(next, log)); buildFunc(next => PerformanceLogging.Middleware(next, log)); buildFunc(next => new MonitoringMiddleware(next, HealthCheck).Invoke); buildFunc.UseNancy(opt => opt.Bootstrapper = new Bootstrapper(log)); }); } private ILogger ConfigureLogger() { return new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.ColoredConsole( LogEventLevel.Verbose, "{NewLine}{Timestamp:HH:mm:ss} [{Level}] ({CorrelationToken}) {Message}{NewLine}{Exception}") .CreateLogger(); } private const string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=ShoppingCart;Integrated Security=True"; private readonly int threshold = 1000; public async Task HealthCheck() { using (var conn = new SqlConnection(connectionString)) { var count = (await conn.QueryAsync("select count(ID) from ShoppingCart")).Single(); return count > this.threshold; } } } } ================================================ FILE: src/Chapter09/ShoppingCart/database-scripts/create-shopping-cart-db.sql ================================================ CREATE DATABASE ShoppingCart GO USE [ShoppingCart] GO CREATE TABLE [dbo].[ShoppingCart]( [ID] int IDENTITY(1,1) PRIMARY KEY, [UserId] [bigint] NOT NULL, CONSTRAINT ShoppingCartUnique UNIQUE([ID], [UserID]) ) GO CREATE INDEX ShoppingCart_UserId ON [dbo].[ShoppingCart] (UserId) GO CREATE TABLE [dbo].[ShoppingCartItems]( [ID] int IDENTITY(1,1) PRIMARY KEY, [ShoppingCartId] [int] NOT NULL, [ProductCatalogId] [bigint] NOT NULL, [ProductName] [nvarchar](100) NOT NULL, [ProductDescription] [nvarchar](500) NULL, [Amount] [int] NOT NULL, [Currency] [nvarchar](5) NOT NULL ) GO ALTER TABLE [dbo].[ShoppingCartItems] WITH CHECK ADD CONSTRAINT [FK_ShoppingCart] FOREIGN KEY([ShoppingCartId]) REFERENCES [dbo].[ShoppingCart] ([Id]) GO ALTER TABLE [dbo].[ShoppingCartItems] CHECK CONSTRAINT [FK_ShoppingCart] GO CREATE INDEX ShoppingCartItems_ShoppingCartId ON [dbo].[ShoppingCartItems] (ShoppingCartId) GO CREATE TABLE [dbo].[EventStore]( [ID] int IDENTITY(1,1) PRIMARY KEY, [Name] [nvarchar](100) NOT NULL, [OccuredAt] [datetimeoffset] NOT NULL, [Content][nvarchar](max) NOT NULL ) GO ================================================ FILE: src/Chapter09/ShoppingCart/project.json ================================================ { "dependencies": { "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Owin": "1.0.0", "Nancy": "2.0.0-barneyrubble", "Polly": "4.2.1", "Dapper": "1.50.0-rc2a", "Serilog": "2.0.0-rc-600", "Serilog.Sinks.ColoredConsole": "2.0.0-beta-1001" }, "runtimes": { "win10-x64": "" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ], "dependencies": { "System.Net.Http": "4.0.1-rc2-24027" } }, "net461": { "buildOptions": {"define": ["net461"]}, "frameworkAssemblies": { "System.Net.Http": "4.0.0.0", "System.Runtime": "4.0.20.0" }, "dependencies": { "EventStore.Client": "3.3.1", "Microsoft.CSharp": "4.0.0" } } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "HelloMicroservices" } } ================================================ FILE: src/Chapter09/ShoppingCart/web.config ================================================ ================================================ FILE: src/Chapter11/.idea.ch11/riderModule.iml ================================================ ================================================ FILE: src/Chapter11/HelloMicroservicesPlatform/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter11/HelloMicroservicesPlatform/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter11/HelloMicroservicesPlatform/HelloMicroservicesPlatform.xproj ================================================  14.0.25123 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) daebd923-b213-4ca5-a240-b96686aec974 HelloMicroservicesPlatform .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter11/HelloMicroservicesPlatform/Program.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; namespace HelloMicroservicesPlatform { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter11/HelloMicroservicesPlatform/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "HelloMicroservicesPlatform": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter11/HelloMicroservicesPlatform/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter11/HelloMicroservicesPlatform/Startup.cs ================================================ namespace HelloMicroservicesPlatform { using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using MicroserviceNET.Logging; using MicroserviceNET.Auth; using MicroserviceNET.Platform; using Serilog; using Serilog.Events; using Nancy; using Nancy.Owin; using Nancy.TinyIoc; using Nancy.Bootstrapper; public class Startup { public void Configure(IApplicationBuilder app) { app.UseOwin() .UseMonitoringAndLogging(ConfigureLogger(), HealthCheck) .UseAuthPlatform("test-scope") .UseNancy(); } private ILogger ConfigureLogger() { return new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.ColoredConsole( LogEventLevel.Verbose, "{NewLine}{Timestamp:HH:mm:ss} [{Level}] ({CorrelationToken}) {Message}{NewLine}{Exception}") .CreateLogger(); } private static Task HealthCheck() { return Task.FromResult(true); } } public class Bootstrapper : DefaultNancyBootstrapper { protected override void RequestStartup( TinyIoCContainer container, IPipelines pipelines, NancyContext context) { base.RequestStartup(container, pipelines, context); container.UseHttpClientFactory(context); } } public class Hello : NancyModule { public Hello(IHttpClientFactory clientFactory) { Get("/", async (_, __) => { var client = await clientFactory.Create( new Uri("http://otherservice/"), "scope_for_other_microservice"); var resp = await client.GetAsync("/some/path").ConfigureAwait(false); return resp.StatusCode; }); } } } ================================================ FILE: src/Chapter11/HelloMicroservicesPlatform/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Owin": "1.0.0", "Serilog": "2.0.0-rc-600", "MicroserviceNET.Platform": { "target": "project" }, "Serilog.Sinks.ColoredConsole": "2.0.0-beta-700" }, "tools": { }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "HelloMicroservicesPlatform" } } ================================================ FILE: src/Chapter11/HelloMicroservicesPlatform/web.config ================================================ ================================================ FILE: src/Chapter11/MicroserivceNET.Auth/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter11/MicroserivceNET.Auth/Application_Packages/LibOwin.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. // Copyright (c) Damian Hickey. All rights reserved. See License.txt in the project root for license information. // https://github.com/damianh/LibOwin // Modifying this file may result in difficulties when upgrading the package. // All types are internal. Add a LIBOWIN_PUBLIC compilation symbol to make them public. namespace LibOwin.Infrastructure { using System; using System.Collections; using System.Collections.Generic; using System.Linq; internal static partial class Constants { internal const string Https = "HTTPS"; internal const string HttpDateFormat = "r"; internal static partial class Headers { internal const string ContentType = "Content-Type"; internal const string CacheControl = "Cache-Control"; internal const string MediaType = "Media-Type"; internal const string Accept = "Accept"; internal const string Host = "Host"; internal const string ETag = "ETag"; internal const string Location = "Location"; internal const string ContentLength = "Content-Length"; internal const string SetCookie = "Set-Cookie"; internal const string Expires = "Expires"; } } internal struct HeaderSegment : IEquatable { private readonly StringSegment _formatting; private readonly StringSegment _data; // // Initializes a new instance of the class. // public HeaderSegment(StringSegment formatting, StringSegment data) { _formatting = formatting; _data = data; } public StringSegment Formatting { get { return _formatting; } } public StringSegment Data { get { return _data; } } #region Equality members public bool Equals(HeaderSegment other) { return _formatting.Equals(other._formatting) && _data.Equals(other._data); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegment && Equals((HeaderSegment)obj); } public override int GetHashCode() { unchecked { return (_formatting.GetHashCode() * 397) ^ _data.GetHashCode(); } } public static bool operator ==(HeaderSegment left, HeaderSegment right) { return left.Equals(right); } public static bool operator !=(HeaderSegment left, HeaderSegment right) { return !left.Equals(right); } #endregion } internal struct HeaderSegmentCollection : IEnumerable, IEquatable { private readonly string[] _headers; public HeaderSegmentCollection(string[] headers) { _headers = headers; } #region Equality members public bool Equals(HeaderSegmentCollection other) { return Equals(_headers, other._headers); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegmentCollection && Equals((HeaderSegmentCollection)obj); } public override int GetHashCode() { return (_headers != null ? _headers.GetHashCode() : 0); } public static bool operator ==(HeaderSegmentCollection left, HeaderSegmentCollection right) { return left.Equals(right); } public static bool operator !=(HeaderSegmentCollection left, HeaderSegmentCollection right) { return !left.Equals(right); } #endregion public Enumerator GetEnumerator() { return new Enumerator(_headers); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } internal struct Enumerator : IEnumerator { private readonly string[] _headers; private int _index; private string _header; private int _headerLength; private int _offset; private int _leadingStart; private int _leadingEnd; private int _valueStart; private int _valueEnd; private int _trailingStart; private Mode _mode; private static readonly string[] NoHeaders = new string[0]; public Enumerator(string[] headers) { _headers = headers ?? NoHeaders; _header = string.Empty; _headerLength = -1; _index = -1; _offset = -1; _leadingStart = -1; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; _mode = Mode.Leading; } private enum Mode { Leading, Value, ValueQuoted, Trailing, Produce, } private enum Attr { Value, Quote, Delimiter, Whitespace } public HeaderSegment Current { get { return new HeaderSegment( new StringSegment(_header, _leadingStart, _leadingEnd - _leadingStart), new StringSegment(_header, _valueStart, _valueEnd - _valueStart)); } } object IEnumerator.Current { get { return Current; } } public void Dispose() { } public bool MoveNext() { while (true) { if (_mode == Mode.Produce) { _leadingStart = _trailingStart; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; if (_offset == _headerLength && _leadingStart != -1 && _leadingStart != _offset) { // Also produce trailing whitespace _leadingEnd = _offset; return true; } _mode = Mode.Leading; } // if end of a string if (_offset == _headerLength) { ++_index; _offset = -1; _leadingStart = 0; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; // if that was the last string if (_index == _headers.Length) { // no more move nexts return false; } // grab the next string _header = _headers[_index] ?? string.Empty; _headerLength = _header.Length; } while (true) { ++_offset; char ch = _offset == _headerLength ? (char)0 : _header[_offset]; // todo - array of attrs Attr attr = char.IsWhiteSpace(ch) ? Attr.Whitespace : ch == '\"' ? Attr.Quote : (ch == ',' || ch == (char)0) ? Attr.Delimiter : Attr.Value; switch (_mode) { case Mode.Leading: switch (attr) { case Attr.Delimiter: _leadingEnd = _offset; _mode = Mode.Produce; break; case Attr.Quote: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.ValueQuoted; break; case Attr.Value: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; case Mode.Value: switch (attr) { case Attr.Quote: _mode = Mode.ValueQuoted; break; case Attr.Delimiter: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; break; case Attr.Value: // more break; case Attr.Whitespace: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Trailing; break; } break; case Mode.ValueQuoted: switch (attr) { case Attr.Quote: _mode = Mode.Value; break; case Attr.Delimiter: if (ch == (char)0) { _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; } break; case Attr.Value: case Attr.Whitespace: // more break; } break; case Mode.Trailing: switch (attr) { case Attr.Delimiter: _mode = Mode.Produce; break; case Attr.Quote: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.ValueQuoted; break; case Attr.Value: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; } if (_mode == Mode.Produce) { return true; } } } } public void Reset() { _index = 0; _offset = 0; _leadingStart = 0; _leadingEnd = 0; _valueStart = 0; _valueEnd = 0; } } } internal struct StringSegment : IEquatable { private readonly string _buffer; private readonly int _offset; private readonly int _count; // // Initializes a new instance of the class. // public StringSegment(string buffer, int offset, int count) { _buffer = buffer; _offset = offset; _count = count; } public string Buffer { get { return _buffer; } } public int Offset { get { return _offset; } } public int Count { get { return _count; } } public string Value { get { return _offset == -1 ? null : _buffer.Substring(_offset, _count); } } public bool HasValue { get { return _offset != -1 && _count != 0 && _buffer != null; } } #region Equality members public bool Equals(StringSegment other) { return string.Equals(_buffer, other._buffer) && _offset == other._offset && _count == other._count; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is StringSegment && Equals((StringSegment)obj); } public override int GetHashCode() { unchecked { int hashCode = (_buffer != null ? _buffer.GetHashCode() : 0); hashCode = (hashCode * 397) ^ _offset; hashCode = (hashCode * 397) ^ _count; return hashCode; } } public static bool operator ==(StringSegment left, StringSegment right) { return left.Equals(right); } public static bool operator !=(StringSegment left, StringSegment right) { return !left.Equals(right); } #endregion public bool StartsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public bool EndsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset + _count - textLength, text, 0, textLength, comparisonType) == 0; } public bool Equals(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count != textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public string Substring(int offset, int length) { return _buffer.Substring(_offset + offset, length); } public StringSegment Subsegment(int offset, int length) { return new StringSegment(_buffer, _offset + offset, length); } public override string ToString() { return Value ?? string.Empty; } } internal static partial class OwinHelpers { private static readonly Action AddCookieCallback = (name, value, state) => { var dictionary = (IDictionary)state; if (!dictionary.ContainsKey(name)) { dictionary.Add(name, value); } }; private static readonly char[] SemicolonAndComma = new[] { ';', ',' }; internal static IDictionary GetCookies(IOwinRequest request) { var cookies = request.Get>("Microsoft.Owin.Cookies#dictionary"); if (cookies == null) { cookies = new Dictionary(StringComparer.Ordinal); request.Set("Microsoft.Owin.Cookies#dictionary", cookies); } string text = GetHeader(request.Headers, "Cookie"); if (request.Get("Microsoft.Owin.Cookies#text") != text) { cookies.Clear(); ParseDelimited(text, SemicolonAndComma, AddCookieCallback, cookies); request.Set("Microsoft.Owin.Cookies#text", text); } return cookies; } internal static void ParseDelimited(string text, char[] delimiters, Action callback, object state) { int textLength = text.Length; int equalIndex = text.IndexOf('='); if (equalIndex == -1) { equalIndex = textLength; } int scanIndex = 0; while (scanIndex < textLength) { int delimiterIndex = text.IndexOfAny(delimiters, scanIndex); if (delimiterIndex == -1) { delimiterIndex = textLength; } if (equalIndex < delimiterIndex) { while (scanIndex != equalIndex && char.IsWhiteSpace(text[scanIndex])) { ++scanIndex; } string name = text.Substring(scanIndex, equalIndex - scanIndex); string value = text.Substring(equalIndex + 1, delimiterIndex - equalIndex - 1); callback( Uri.UnescapeDataString(name.Replace('+', ' ')), Uri.UnescapeDataString(value.Replace('+', ' ')), state); equalIndex = text.IndexOf('=', delimiterIndex); if (equalIndex == -1) { equalIndex = textLength; } } scanIndex = delimiterIndex + 1; } } } internal static partial class OwinHelpers { public static string GetHeader(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : string.Join(",", values); } public static IEnumerable GetHeaderSplit(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : GetHeaderSplitImplementation(values); } private static IEnumerable GetHeaderSplitImplementation(string[] values) { foreach (var segment in new HeaderSegmentCollection(values)) { if (segment.Data.HasValue) { yield return DeQuote(segment.Data.Value); } } } public static string[] GetHeaderUnmodified(IDictionary headers, string key) { if (headers == null) { throw new ArgumentNullException("headers"); } string[] values; return headers.TryGetValue(key, out values) ? values : null; } public static void SetHeader(IDictionary headers, string key, string value) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (string.IsNullOrWhiteSpace(value)) { headers.Remove(key); } else { headers[key] = new[] { value }; } } public static void SetHeaderJoined(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = new[] { string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } // Quote items that contain comas and are not already quoted. private static string QuoteIfNeeded(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Contains(',')) { if (value[0] != '"' || value[value.Length - 1] != '"') { value = '"' + value + '"'; } } return value; } private static string DeQuote(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"') { value = value.Substring(1, value.Length - 2); } return value; } public static void SetHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = values; } } public static void SetHeaderUnmodified(IDictionary headers, string key, IEnumerable values) { if (headers == null) { throw new ArgumentNullException("headers"); } headers[key] = values.ToArray(); } public static void AppendHeader(IDictionary headers, string key, string values) { if (string.IsNullOrWhiteSpace(values)) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeader(headers, key, values); } else { headers[key] = new[] { existing + "," + values }; } } public static void AppendHeaderJoined(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeaderJoined(headers, key, values); } else { headers[key] = new[] { existing + "," + string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } public static void AppendHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string[] existing = GetHeaderUnmodified(headers, key); if (existing == null) { SetHeaderUnmodified(headers, key, values); } else { SetHeaderUnmodified(headers, key, existing.Concat(values)); } } } internal static partial class OwinHelpers { private static readonly Action AppendItemCallback = (name, value, state) => { var dictionary = (IDictionary>)state; List existing; if (!dictionary.TryGetValue(name, out existing)) { dictionary.Add(name, new List(1) { value }); } else { existing.Add(value); } }; private static readonly char[] AmpersandAndSemicolon = new[] { '&', ';' }; internal static IDictionary GetQuery(IOwinRequest request) { var query = request.Get>("Microsoft.Owin.Query#dictionary"); if (query == null) { query = new Dictionary(StringComparer.OrdinalIgnoreCase); request.Set("Microsoft.Owin.Query#dictionary", query); } string text = request.QueryString.Value; if (request.Get("Microsoft.Owin.Query#text") != text) { query.Clear(); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, AmpersandAndSemicolon, AppendItemCallback, accumulator); foreach (var kv in accumulator) { query.Add(kv.Key, kv.Value.ToArray()); } request.Set("Microsoft.Owin.Query#text", text); } return query; } internal static IFormCollection GetForm(string text) { IDictionary form = new Dictionary(StringComparer.OrdinalIgnoreCase); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, new[] { '&' }, AppendItemCallback, accumulator); foreach (var kv in accumulator) { form.Add(kv.Key, kv.Value.ToArray()); } return new FormCollection(form); } internal static string GetJoinedValue(IDictionary store, string key) { string[] values = GetUnmodifiedValues(store, key); return values == null ? null : string.Join(",", values); } internal static string[] GetUnmodifiedValues(IDictionary store, string key) { if (store == null) { throw new ArgumentNullException("store"); } string[] values; return store.TryGetValue(key, out values) ? values : null; } } internal static partial class OwinHelpers { internal static string GetHost(IOwinRequest request) { IHeaderDictionary headers = request.Headers; string host = GetHeader(headers, "Host"); if (!string.IsNullOrWhiteSpace(host)) { return host; } string localIpAddress = request.LocalIpAddress ?? "localhost"; var localPort = request.Get(OwinConstants.CommonKeys.LocalPort); return string.IsNullOrWhiteSpace(localPort) ? localIpAddress : (localIpAddress + ":" + localPort); } } } namespace LibOwin { using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Text; using System.Threading; using System.Threading.Tasks; using LibOwin.Infrastructure; #if LIBOWIN_PUBLIC public partial class CookieOptions { } public partial class FormCollection { } public partial class HeaderDictionary { } public partial struct HostString { } public partial interface IFormCollection { } public partial interface IHeaderDictionary { } public partial interface IOwinContext { } public partial interface IOwinRequest { } public partial interface IOwinResponse { } public partial interface IReadableStringCollection { } public partial class OwinContext { } public partial class OwinRequest { } public partial class OwinResponse { } public partial struct PathString { } public partial struct QueryString { } public partial class ReadableStringCollection { } public partial class RequestCookieCollection { } public partial class ResponseCookieCollection { } public partial class IOwinResponseExtension { } #endif /// /// Options used to create a new cookie. /// partial class CookieOptions { /// /// Creates a default cookie with a path of '/'. /// public CookieOptions() { Path = "/"; } /// /// Gets or sets the domain to associate the cookie with. /// /// The domain to associate the cookie with. public string Domain { get; set; } /// /// Gets or sets the cookie path. /// /// The cookie path. public string Path { get; set; } /// /// Gets or sets the expiration date and time for the cookie. /// /// The expiration date and time for the cookie. public DateTime? Expires { get; set; } /// /// Gets or sets a value that indicates whether to transmit the cookie using Secure Sockets Layer (SSL)—that is, over HTTPS only. /// /// true to transmit the cookie only over an SSL connection (HTTPS); otherwise, false. public bool Secure { get; set; } /// /// Gets or sets a value that indicates whether a cookie is accessible by client-side script. /// /// true if a cookie is accessible by client-side script; otherwise, false. public bool HttpOnly { get; set; } } /// /// Contains the parsed form values. /// partial class FormCollection : ReadableStringCollection, IFormCollection { /// /// Initializes a new instance of the class. /// /// The store for the form. public FormCollection(IDictionary store) : base(store) {} } /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial class HeaderDictionary : IHeaderDictionary { /// /// Initializes a new instance of the class. /// /// The underlying data store. public HeaderDictionary(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Gets an that contains the keys in the ;. /// /// An that contains the keys in the . public ICollection Keys { get { return Store.Keys; } } /// /// /// public ICollection Values { get { return Store.Values; } } /// /// Gets the number of elements contained in the ;. /// /// The number of elements contained in the . public int Count { get { return Store.Count; } } /// /// Gets a value that indicates whether the is in read-only mode. /// /// true if the is in read-only mode; otherwise, false. public bool IsReadOnly { get { return Store.IsReadOnly; } } /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string this[string key] { get { return Get(key); } set { Set(key, value); } } /// /// Throws KeyNotFoundException if the key is not present. /// /// The header name. /// string[] IDictionary.this[string key] { get { return Store[key]; } set { Store[key] = value; } } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Get the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string Get(string key) { return OwinHelpers.GetHeader(Store, key); } /// /// Get the associated values from the collection without modification. /// /// The header name. /// the associated value from the collection without modification, or null if the key is not present. public IList GetValues(string key) { return OwinHelpers.GetHeaderUnmodified(Store, key); } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. public IList GetCommaSeparatedValues(string key) { IEnumerable values = OwinHelpers.GetHeaderSplit(Store, key); return values == null ? null : values.ToList(); } /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. public void Append(string key, string value) { OwinHelpers.AppendHeader(Store, key, value); } /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. public void AppendValues(string key, params string[] values) { OwinHelpers.AppendHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. public void AppendCommaSeparatedValues(string key, params string[] values) { OwinHelpers.AppendHeaderJoined(Store, key, values); } /// /// Sets a specific header value. /// /// The header name. /// The header value. public void Set(string key, string value) { OwinHelpers.SetHeader(Store, key, value); } /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. public void SetValues(string key, params string[] values) { OwinHelpers.SetHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. public void SetCommaSeparatedValues(string key, params string[] values) { OwinHelpers.SetHeaderJoined(Store, key, values); } /// /// Adds the given header and values to the collection. /// /// The header name. /// The header values. public void Add(string key, string[] value) { Store.Add(key, value); } /// /// Determines whether the contains a specific key. /// /// The key. /// true if the contains a specific key; otherwise, false. public bool ContainsKey(string key) { return Store.ContainsKey(key); } /// /// Removes the given header from the collection. /// /// The header name. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(string key) { return Store.Remove(key); } /// /// Retrieves a value from the dictionary. /// /// The header name. /// The value. /// true if the contains the key; otherwise, false. public bool TryGetValue(string key, out string[] value) { return Store.TryGetValue(key, out value); } /// /// Adds a new list of items to the collection. /// /// The item to add. public void Add(KeyValuePair item) { Store.Add(item); } /// /// Clears the entire list of objects. /// public void Clear() { Store.Clear(); } /// /// Returns a value indicating whether the specified object occurs within this collection. /// /// The item. /// true if the specified object occurs within this collection; otherwise, false. public bool Contains(KeyValuePair item) { return Store.Contains(item); } /// /// Copies the elements to a one-dimensional Array instance at the specified index. /// /// The one-dimensional Array that is the destination of the specified objects copied from the . /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) { Store.CopyTo(array, arrayIndex); } /// /// Removes the given item from the the collection. /// /// The item. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(KeyValuePair item) { return Store.Remove(item); } } /// /// Represents the host portion of a Uri can be used to construct Uri's properly formatted and encoded for use in /// HTTP headers. /// partial struct HostString : IEquatable { private readonly string _value; /// /// Creates a new HostString without modification. The value should be Unicode rather than punycode, and may have a port. /// IPv4 and IPv6 addresses are also allowed, and also may have ports. /// /// public HostString(string value) { _value = value; } /// /// Returns the original value from the constructor. /// public string Value { get { return _value; } } /// /// Returns the value as normalized by ToUriComponent(). /// /// public override string ToString() { return ToUriComponent(); } /// /// Returns the value properly formatted and encoded for use in a URI in a HTTP header. /// Any Unicode is converted to punycode. IPv6 addresses will have brackets added if they are missing. /// /// public string ToUriComponent() { int index; if (string.IsNullOrEmpty(_value)) { return string.Empty; } else if (_value.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port return _value; } else if ((index = _value.IndexOf(':')) >= 0 && index < _value.Length - 1 && _value.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons return "[" + _value + "]"; } else if (index >= 0) { // Has a port string port = _value.Substring(index); var mapping = new IdnMapping(); return mapping.GetAscii(_value, 0, index) + port; } else { var mapping = new IdnMapping(); return mapping.GetAscii(_value); } } /// /// Creates a new HostString from the given uri component. /// Any punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(string uriComponent) { if (!string.IsNullOrEmpty(uriComponent)) { int index; if (uriComponent.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port } else if ((index = uriComponent.IndexOf(':')) >= 0 && index < uriComponent.Length - 1 && uriComponent.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons } else if (uriComponent.IndexOf("xn--", StringComparison.Ordinal) >= 0) { // Contains punycode if (index >= 0) { // Has a port string port = uriComponent.Substring(index); IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent, 0, index) + port; } else { IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent); } } } return new HostString(uriComponent); } /// /// Creates a new HostString from the host and port of the give Uri instance. /// Punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new HostString(uri.GetComponents( UriComponents.NormalizedHost | // Always convert punycode to Unicode. UriComponents.HostAndPort, UriFormat.Unescaped)); } /// /// Compares the equality of the Value property, ignoring case. /// /// /// public bool Equals(HostString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares against the given object only if it is a HostString. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HostString && Equals((HostString)obj); } /// /// Gets a hash code for the value. /// /// public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(HostString left, HostString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(HostString left, HostString right) { return !left.Equals(right); } } /// /// Contains the parsed form values. /// partial interface IFormCollection : IReadableStringCollection {} /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial interface IHeaderDictionary : IReadableStringCollection, IDictionary { /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. new string this[string key] { get; set; } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. IList GetCommaSeparatedValues(string key); /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. void Append(string key, string value); /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. void AppendValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. void AppendCommaSeparatedValues(string key, params string[] values); /// /// Sets a specific header value. /// /// The header name. /// The header value. void Set(string key, string value); /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. void SetValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. void SetCommaSeparatedValues(string key, params string[] values); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinContext { /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. IOwinRequest Request { get; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. IOwinResponse Response { get; } /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. TextWriter TraceOutput { get; set; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinContext Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinRequest { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or set the HTTP method. /// /// The HTTP method. string Method { get; set; } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. string Scheme { get; set; } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. bool IsSecure { get; } /// /// Gets or set the Host header. May include the port. /// /// The Host header. HostString Host { get; set; } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. PathString PathBase { get; set; } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. PathString Path { get; set; } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. QueryString QueryString { get; set; } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. IReadableStringCollection Query { get; } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. Uri Uri { get; } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. string Protocol { get; set; } /// /// Gets the request headers. /// /// The request headers. IHeaderDictionary Headers { get; } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. RequestCookieCollection Cookies { get; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. string CacheControl { get; set; } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. string MediaType { get; set; } /// /// Gets or set the Accept header. /// /// The Accept header. string Accept { get; set; } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. Stream Body { get; set; } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. CancellationToken CallCancelled { get; set; } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. string LocalIpAddress { get; set; } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. int? LocalPort { get; set; } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. string RemoteIpAddress { get; set; } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. int? RemotePort { get; set; } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. ClaimsPrincipal User { get; set; } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. Task ReadFormAsync(); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinRequest Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinResponse { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. int StatusCode { get; set; } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. string ReasonPhrase { get; set; } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. string Protocol { get; set; } /// /// Gets the response header collection. /// /// The response header collection. IHeaderDictionary Headers { get; } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. ResponseCookieCollection Cookies { get; } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. long? ContentLength { get; set; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Expires header. /// /// The Expires header. DateTimeOffset? Expires { get; set; } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. string ETag { get; set; } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. Stream Body { get; set; } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. void OnSendingHeaders(Action callback, object state); /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. void Redirect(string location); /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. void Write(string text); /// /// Writes the given bytes to the response body stream. /// /// The response data. void Write(byte[] data); /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. void Write(byte[] data, int offset, int count); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(string text); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(string text, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, int offset, int count, CancellationToken token); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinResponse Set(string key, T value); } /// /// Accessors for headers, query, forms, etc. /// partial interface IReadableStringCollection : IEnumerable> { /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string this[string key] { get; } // Joined /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string Get(string key); // Joined /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// IList GetValues(string key); // Raw } internal static class OwinConstants { #region OWIN v1.0.0 - 3.2.1. Request Data // http://owin.org/spec/owin-1.0.0.html public const string RequestScheme = "owin.RequestScheme"; public const string RequestMethod = "owin.RequestMethod"; public const string RequestPathBase = "owin.RequestPathBase"; public const string RequestPath = "owin.RequestPath"; public const string RequestQueryString = "owin.RequestQueryString"; public const string RequestProtocol = "owin.RequestProtocol"; public const string RequestHeaders = "owin.RequestHeaders"; public const string RequestBody = "owin.RequestBody"; public const string RequestUser = "owin.RequestUser"; //owin 1.0.1 #endregion #region OWIN v1.0.0 - 3.2.2. Response Data // http://owin.org/spec/owin-1.0.0.html public const string ResponseStatusCode = "owin.ResponseStatusCode"; public const string ResponseReasonPhrase = "owin.ResponseReasonPhrase"; public const string ResponseProtocol = "owin.ResponseProtocol"; public const string ResponseHeaders = "owin.ResponseHeaders"; public const string ResponseBody = "owin.ResponseBody"; #endregion #region OWIN v1.0.0 - 3.2.3. Other Data // http://owin.org/spec/owin-1.0.0.html public const string CallCancelled = "owin.CallCancelled"; public const string OwinVersion = "owin.Version"; #endregion #region OWIN Keys for IAppBuilder.Properties internal static class Builder { public const string AddSignatureConversion = "builder.AddSignatureConversion"; public const string DefaultApp = "builder.DefaultApp"; } #endregion #region OWIN Key Guidelines and Common Keys - 6. Common keys // http://owin.org/spec/CommonKeys.html internal static class CommonKeys { public const string ClientCertificate = "ssl.ClientCertificate"; public const string RemoteIpAddress = "server.RemoteIpAddress"; public const string RemotePort = "server.RemotePort"; public const string LocalIpAddress = "server.LocalIpAddress"; public const string LocalPort = "server.LocalPort"; public const string IsLocal = "server.IsLocal"; public const string TraceOutput = "host.TraceOutput"; public const string Addresses = "host.Addresses"; public const string AppName = "host.AppName"; public const string Capabilities = "server.Capabilities"; public const string OnSendingHeaders = "server.OnSendingHeaders"; public const string OnAppDisposing = "host.OnAppDisposing"; public const string Scheme = "scheme"; public const string Host = "host"; public const string Port = "port"; public const string Path = "path"; } #endregion #region SendFiles v0.3.0 // http://owin.org/extensions/owin-SendFile-Extension-v0.3.0.htm internal static class SendFiles { // 3.1. Startup public const string Version = "sendfile.Version"; public const string Support = "sendfile.Support"; public const string Concurrency = "sendfile.Concurrency"; // 3.2. Per Request public const string SendAsync = "sendfile.SendAsync"; } #endregion #region Opaque v0.3.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class OpaqueConstants { // 3.1. Startup public const string Version = "opaque.Version"; // 3.2. Per Request public const string Upgrade = "opaque.Upgrade"; // 5. Consumption public const string Stream = "opaque.Stream"; // public const string Version = "opaque.Version"; // redundant, declared above public const string CallCancelled = "opaque.CallCancelled"; } #endregion #region WebSocket v0.4.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class WebSocket { // 3.1. Startup public const string Version = "websocket.Version"; // 3.2. Per Request public const string Accept = "websocket.Accept"; // 4. Accept public const string SubProtocol = "websocket.SubProtocol"; // 5. Consumption public const string SendAsync = "websocket.SendAsync"; public const string ReceiveAsync = "websocket.ReceiveAsync"; public const string CloseAsync = "websocket.CloseAsync"; // public const string Version = "websocket.Version"; // redundant, declared above public const string CallCancelled = "websocket.CallCancelled"; public const string ClientCloseStatus = "websocket.ClientCloseStatus"; public const string ClientCloseDescription = "websocket.ClientCloseDescription"; } #endregion #region Security v0.1.0 // http://owin.org/extensions/owin-Security-Extension-v0.1.0.htm internal static class Security { // 3.2. Per Request public const string User = "server.User"; public const string Authenticate = "security.Authenticate"; // 3.3. Response public const string SignIn = "security.SignIn"; public const string SignOut = "security.SignOut"; public const string SignOutProperties = "security.SignOutProperties"; public const string Challenge = "security.Challenge"; } #endregion } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinContext : IOwinContext { /// /// Create a new context with only request and response header collections. /// public OwinContext() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Create a new wrapper. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinContext(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. public virtual IOwinRequest Request { get; private set; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. public virtual IOwinResponse Response { get; private set; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. public virtual TextWriter TraceOutput { get { return Get(OwinConstants.CommonKeys.TraceOutput); } set { Set(OwinConstants.CommonKeys.TraceOutput, value); } } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinContext Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinRequest : IOwinRequest { /// /// Create a new context with only request and response header collections. /// public OwinRequest() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Create a new environment wrapper exposing request properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinRequest(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or set the HTTP method. /// /// The HTTP method. public virtual string Method { get { return Get(OwinConstants.RequestMethod); } set { Set(OwinConstants.RequestMethod, value); } } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. public virtual string Scheme { get { return Get(OwinConstants.RequestScheme); } set { Set(OwinConstants.RequestScheme, value); } } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. public virtual bool IsSecure { get { return string.Equals(Scheme, Constants.Https, StringComparison.OrdinalIgnoreCase); } } /// /// Gets or set the Host header. May include the port. /// /// The Host header. public virtual HostString Host { get { return new HostString(OwinHelpers.GetHost(this)); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Host, value.Value); } } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. public virtual PathString PathBase { get { return new PathString(Get(OwinConstants.RequestPathBase)); } set { Set(OwinConstants.RequestPathBase, value.Value); } } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. public virtual PathString Path { get { return new PathString(Get(OwinConstants.RequestPath)); } set { Set(OwinConstants.RequestPath, value.Value); } } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. public virtual QueryString QueryString { get { return new QueryString(Get(OwinConstants.RequestQueryString)); } set { Set(OwinConstants.RequestQueryString, value.Value); } } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. public virtual IReadableStringCollection Query { get { return new ReadableStringCollection(OwinHelpers.GetQuery(this)); } } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. public virtual Uri Uri { get { return new Uri(Scheme + "://" + Host + PathBase + Path + QueryString); } } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. public virtual string Protocol { get { return Get(OwinConstants.RequestProtocol); } set { Set(OwinConstants.RequestProtocol, value); } } /// /// Gets the request headers. /// /// The request headers. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.RequestHeaders); } } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. public RequestCookieCollection Cookies { get { return new RequestCookieCollection(OwinHelpers.GetCookies(this)); } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. public virtual string CacheControl { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.CacheControl); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.CacheControl, value); } } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. public virtual string MediaType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.MediaType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.MediaType, value); } } /// /// Gets or set the Accept header. /// /// The Accept header. public virtual string Accept { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Accept); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Accept, value); } } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. public virtual Stream Body { get { return Get(OwinConstants.RequestBody); } set { Set(OwinConstants.RequestBody, value); } } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. public virtual CancellationToken CallCancelled { get { return Get(OwinConstants.CallCancelled); } set { Set(OwinConstants.CallCancelled, value); } } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. public virtual string LocalIpAddress { get { return Get(OwinConstants.CommonKeys.LocalIpAddress); } set { Set(OwinConstants.CommonKeys.LocalIpAddress, value); } } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. public virtual int? LocalPort { get { int value; if (int.TryParse(LocalPortString, out value)) { return value; } return null; } set { if (value.HasValue) { LocalPortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.LocalPort); } } } private string LocalPortString { get { return Get(OwinConstants.CommonKeys.LocalPort); } set { Set(OwinConstants.CommonKeys.LocalPort, value); } } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. public virtual string RemoteIpAddress { get { return Get(OwinConstants.CommonKeys.RemoteIpAddress); } set { Set(OwinConstants.CommonKeys.RemoteIpAddress, value); } } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. public virtual int? RemotePort { get { int value; if (int.TryParse(RemotePortString, out value)) { return value; } return null; } set { if (value.HasValue) { RemotePortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.RemotePort); } } } private string RemotePortString { get { return Get(OwinConstants.CommonKeys.RemotePort); } set { Set(OwinConstants.CommonKeys.RemotePort, value); } } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. public virtual ClaimsPrincipal User { get { var claimsPrincipal = Get(OwinConstants.RequestUser); return claimsPrincipal ?? Get(OwinConstants.Security.User) as ClaimsPrincipal; } set { Set(OwinConstants.RequestUser, value); } } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. public async Task ReadFormAsync() { var form = Get("Microsoft.Owin.Form#collection"); if (form == null) { string text; // Don't close, it prevents re-winding. using (var reader = new StreamReader(Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 4 * 1024, leaveOpen: true)) { text = await reader.ReadToEndAsync(); } form = OwinHelpers.GetForm(text); Set("Microsoft.Owin.Form#collection", form); } return form; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinRequest Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinResponse : IOwinResponse { /// /// Create a new context with only request and response header collections. /// public OwinResponse() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Creates a new environment wrapper exposing response properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinResponse(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. public virtual int StatusCode { get { return Get(OwinConstants.ResponseStatusCode, 200); } set { Set(OwinConstants.ResponseStatusCode, value); } } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. public virtual string ReasonPhrase { get { return Get(OwinConstants.ResponseReasonPhrase); } set { Set(OwinConstants.ResponseReasonPhrase, value); } } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. public virtual string Protocol { get { return Get(OwinConstants.ResponseProtocol); } set { Set(OwinConstants.ResponseProtocol, value); } } /// /// Gets the response header collection. /// /// The response header collection. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.ResponseHeaders); } } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. public virtual ResponseCookieCollection Cookies { get { return new ResponseCookieCollection(Headers); } } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. public virtual long? ContentLength { get { long value; if (long.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentLength), out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentLength, value.Value.ToString(CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.ContentLength); } } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Expires header. /// /// The Expires header. public virtual DateTimeOffset? Expires { get { DateTimeOffset value; if (DateTimeOffset.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Expires), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Expires, value.Value.ToString(Constants.HttpDateFormat, CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.Expires); } } } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. public virtual string ETag { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ETag); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ETag, value); } } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. public virtual Stream Body { get { return Get(OwinConstants.ResponseBody); } set { Set(OwinConstants.ResponseBody, value); } } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. public virtual void OnSendingHeaders(Action callback, object state) { var onSendingHeaders = Get, object>>(OwinConstants.CommonKeys.OnSendingHeaders); if (onSendingHeaders == null) { throw new NotSupportedException(Resources.Exception_MissingOnSendingHeaders); } onSendingHeaders(callback, state); } /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. public virtual void Redirect(string location) { StatusCode = 302; OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Location, location); } /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. public virtual void Write(string text) { Write(Encoding.UTF8.GetBytes(text)); } /// /// Writes the given bytes to the response body stream. /// /// The response data. public virtual void Write(byte[] data) { Write(data, 0, data == null ? 0 : data.Length); } /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. public virtual void Write(byte[] data, int offset, int count) { Body.Write(data, offset, count); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text) { return WriteAsync(text, CancellationToken.None); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text, CancellationToken token) { return WriteAsync(Encoding.UTF8.GetBytes(text), token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data) { return WriteAsync(data, CancellationToken.None); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, CancellationToken token) { return WriteAsync(data, 0, data == null ? 0 : data.Length, token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, int offset, int count, CancellationToken token) { return Body.WriteAsync(data, offset, count, token); } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { return Get(key, default(T)); } private T Get(string key, T fallback) { object value; return Environment.TryGetValue(key, out value) ? (T)value : fallback; } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinResponse Set(string key, T value) { Environment[key] = value; return this; } } /// /// Provides correct escaping for Path and PathBase values when needed to reconstruct a request or redirect URI string /// partial struct PathString : IEquatable { private static readonly Func EscapeDataString = Uri.EscapeDataString; /// /// Represents the empty path. This field is read-only. /// public static readonly PathString Empty = new PathString(String.Empty); private readonly string _value; /// /// Initialize the path string with a given value. This value must be in un-escaped format. Use /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format. /// /// The unescaped path to be assigned to the Value property. public PathString(string value) { if (!String.IsNullOrEmpty(value) && value[0] != '/') { throw new ArgumentException(Resources.Exception_PathMustStartWithSlash, "value"); } _value = value; } /// /// The unescaped path value /// public string Value { get { return _value; } } /// /// True if the path is not empty /// public bool HasValue { get { return !String.IsNullOrEmpty(_value); } } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public override string ToString() { return ToUriComponent(); } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public string ToUriComponent() { if (HasValue) { if (RequiresEscaping(_value)) { // TODO: Measure the cost of this escaping and consider optimizing. return String.Join("/", _value.Split('/').Select(EscapeDataString)); } return _value; } return String.Empty; } // Very conservative, these characters do not need to be escaped in a path. private static bool RequiresEscaping(string value) { for (int i = 0; i < value.Length; i++) { char c = value[i]; // Check conservatively for safe characters. See http://www.ietf.org/rfc/rfc3986.txt bool safeChar = (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '/' || c == '-' || c == '_'); if (!safeChar) { return true; } } return false; } /// /// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a path. /// /// The escaped path as it appears in the URI format. /// The resulting PathString public static PathString FromUriComponent(string uriComponent) { // REVIEW: what is the exactly correct thing to do? return new PathString(Uri.UnescapeDataString(uriComponent)); } /// /// Returns an PathString given the path as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting PathString public static PathString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } // REVIEW: what is the exactly correct thing to do? return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)); } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// public bool StartsWithSegments(PathString other) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { return value1.Length == value2.Length || value1[value2.Length] == '/'; } return false; } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// Any remaining segments from this instance not included in the other instance. /// public bool StartsWithSegments(PathString other, out PathString remaining) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { if (value1.Length == value2.Length || value1[value2.Length] == '/') { remaining = new PathString(value1.Substring(value2.Length)); return true; } } remaining = Empty; return false; } /// /// Adds two PathString instances into a combined PathString value. /// /// The combined PathString value public PathString Add(PathString other) { return new PathString(Value + other.Value); } /// /// Combines a PathString and QueryString into the joined URI formatted string value. /// /// The joined URI formatted string value public string Add(QueryString other) { return ToUriComponent() + other.ToUriComponent(); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public bool Equals(PathString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares this PathString value to another value using a specific StringComparison type /// /// The second PathString for comparison /// The StringComparison type to use /// True if both PathString values are equal public bool Equals(PathString other, StringComparison comparisonType) { return string.Equals(_value, other._value, comparisonType); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is PathString && Equals((PathString)obj, StringComparison.OrdinalIgnoreCase); } /// /// Returns the hash code for the PathString value. The hash code is provided by the OrdinalIgnoreCase implementation. /// /// The hash code public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are equal public static bool operator ==(PathString left, PathString right) { return left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are not equal public static bool operator !=(PathString left, PathString right) { return !left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static PathString operator +(PathString left, PathString right) { return left.Add(right); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static string operator +(PathString left, QueryString right) { return left.Add(right); } } /// /// Provides correct handling for QueryString value when needed to reconstruct a request or redirect URI string /// partial struct QueryString : IEquatable { /// /// Represents the empty query string. This field is read-only. /// public static readonly QueryString Empty = new QueryString(String.Empty); private readonly string _value; /// /// Initalize the query string with a given value. This value must be in escaped and delimited format without /// a leading '?' character. /// /// The query string to be assigned to the Value property. public QueryString(string value) { _value = value; } /// /// Initialize a query string with a single given parameter name and value. The value is /// /// The unencoded parameter name /// The unencoded parameter value public QueryString(string name, string value) { _value = Uri.EscapeDataString(name) + '=' + Uri.EscapeDataString(value); } /// /// The unescaped query string without the leading '?' character /// public string Value { get { return _value; } } /// /// True if the query string is not empty /// public bool HasValue { get { return !String.IsNullOrWhiteSpace(_value); } } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentally /// dangerous are escaped. /// /// The query string value public override string ToString() { return ToUriComponent(); } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentially /// dangerous are escaped. /// /// The query string value public string ToUriComponent() { // Escape things properly so System.Uri doesn't mis-interpret the data. return HasValue ? "?" + _value.Replace("#", "%23") : String.Empty; } /// /// Returns an QueryString given the query as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a query. /// /// The escaped query as it appears in the URI format. /// The resulting QueryString public static QueryString FromUriComponent(string uriComponent) { if (String.IsNullOrEmpty(uriComponent)) { return new QueryString(string.Empty); } if (uriComponent[0] != '?') { throw new ArgumentException(Resources.Exception_QueryStringMustStartWithDelimiter, "uriComponent"); } return new QueryString(uriComponent.Substring(1)); } /// /// Returns an QueryString given the query as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting QueryString public static QueryString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new QueryString(uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped)); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public bool Equals(QueryString other) { return string.Equals(_value, other._value); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is QueryString && Equals((QueryString)obj); } /// /// Returns the hash code for this instance. /// /// public override int GetHashCode() { return (_value != null ? _value.GetHashCode() : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(QueryString left, QueryString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(QueryString left, QueryString right) { return !left.Equals(right); } } /// /// Accessors for query, forms, etc. /// partial class ReadableStringCollection : IReadableStringCollection { /// /// Create a new wrapper /// /// public ReadableStringCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string this[string key] { get { return Get(key); } } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string Get(string key) { return OwinHelpers.GetJoinedValue(Store, key); } /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// public IList GetValues(string key) { string[] values; Store.TryGetValue(key, out values); return values; } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } /// /// A wrapper for the request Cookie header /// partial class RequestCookieCollection : IEnumerable> { /// /// Create a new wrapper /// /// public RequestCookieCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Returns null rather than throwing KeyNotFoundException /// /// /// public string this[string key] { get { string value; Store.TryGetValue(key, out value); return value; } } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } internal static class Resources { internal const string Exception_MissingOnSendingHeaders = "The OWIN key 'server.OnSsoendingHeaders' is not available for this request."; internal const string Exception_PathMustStartWithSlash = "The path must start with a '/' followed by one or more characters."; internal const string Exception_QueryStringMustStartWithDelimiter = "The query string must start with a '?' unless null or empty."; } /// /// A wrapper for the response Set-Cookie header /// partial class ResponseCookieCollection { /// /// Create a new wrapper /// /// public ResponseCookieCollection(IHeaderDictionary headers) { if (headers == null) { throw new ArgumentNullException("headers"); } Headers = headers; } private IHeaderDictionary Headers { get; set; } /// /// Add a new cookie and value /// /// /// public void Append(string key, string value) { Headers.AppendValues(Constants.Headers.SetCookie, Uri.EscapeDataString(key) + "=" + Uri.EscapeDataString(value) + "; path=/"); } /// /// Add a new cookie /// /// /// /// public void Append(string key, string value, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); bool expiresHasValue = options.Expires.HasValue; string setCookieValue = string.Concat( Uri.EscapeDataString(key), "=", Uri.EscapeDataString(value ?? string.Empty), !domainHasValue ? null : "; domain=", !domainHasValue ? null : options.Domain, !pathHasValue ? null : "; path=", !pathHasValue ? null : options.Path, !expiresHasValue ? null : "; expires=", !expiresHasValue ? null : options.Expires.Value.ToString("ddd, dd-MMM-yyyy HH:mm:ss ", CultureInfo.InvariantCulture) + "GMT", !options.Secure ? null : "; secure", !options.HttpOnly ? null : "; HttpOnly"); Headers.AppendValues("Set-Cookie", setCookieValue); } /// /// Sets an expired cookie /// /// public void Delete(string key) { Func predicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); var deleteCookies = new[] { Uri.EscapeDataString(key) + "=; expires=Thu, 01-Jan-1970 00:00:00 GMT" }; IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues == null || existingValues.Count == 0) { Headers.SetValues(Constants.Headers.SetCookie, deleteCookies); } else { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !predicate(value)).Concat(deleteCookies).ToArray()); } } /// /// Sets an expired cookie /// /// /// public void Delete(string key, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); Func rejectPredicate; if (domainHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("domain=" + options.Domain, StringComparison.OrdinalIgnoreCase) != -1; } else if (pathHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("path=" + options.Path, StringComparison.OrdinalIgnoreCase) != -1; } else { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); } IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues != null) { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !rejectPredicate(value)).ToArray()); } Append(key, string.Empty, new CookieOptions { Path = options.Path, Domain = options.Domain, Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), }); } } static partial class IOwinResponseExtension { /// /// Registers for an event that fires when the response headers are sent. /// /// The owin response /// The callback method. /// The callback state. public static void OnSendingHeaders(this IOwinResponse response, Action callback, T state) { if (response == null) { throw new ArgumentNullException("response"); } Action innerCallback = innerState => callback((T)innerState); response.OnSendingHeaders(innerCallback, state); } } } ================================================ FILE: src/Chapter11/MicroserivceNET.Auth/AuthorizationMiddleware.cs ================================================ namespace MicroserviceNET.Auth { using System.IdentityModel.Tokens.Jwt; using System.Threading.Tasks; using LibOwin; using Microsoft.IdentityModel.Tokens; using AppFunc = System.Func, System.Threading.Tasks.Task>; public class Authorization { public static AppFunc Middleware(AppFunc next, string requiredScope) { return env => { var ctx = new OwinContext(env); var principal = ctx.Request.User; if (principal.HasClaim("scope", requiredScope)) return next(env); ctx.Response.StatusCode = 403; return Task.FromResult(0); }; } } public class IdToken { public static AppFunc Middleware(AppFunc next) { return env => { var ctx = new OwinContext(env); if (ctx.Request.Headers.ContainsKey("microservice.NET-end-user")) { var tokenHandler = new JwtSecurityTokenHandler(); SecurityToken token; var userPrincipal = tokenHandler.ValidateToken(ctx.Request.Headers["microservice.NET-end-user"], new TokenValidationParameters(), out token); ctx.Set("pos-end-user", userPrincipal); } return next(env); }; } } } ================================================ FILE: src/Chapter11/MicroserivceNET.Auth/BuildFuncExtensions.cs ================================================ namespace MicroserviceNET.Auth { using BuildFunc = System.Action, System.Threading.Tasks.Task>, System.Func, System.Threading.Tasks.Task>>>; public static class BuildFuncExtensions { public static BuildFunc UseAuthPlatform(this BuildFunc buildFunc, string requiredScope) { buildFunc(next => Authorization.Middleware(next, requiredScope)); buildFunc(next => IdToken.Middleware(next)); return buildFunc; } } } ================================================ FILE: src/Chapter11/MicroserivceNET.Auth/MicroserivceNET.Auth.xproj ================================================  14.0.25123 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 662415b4-3486-4b64-b1f9-718b9ff0879d MicroserivceNET.Auth .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter11/MicroserivceNET.Auth/project.json ================================================ { "version": "1.0.0-*", "dependencies": { "NETStandard.Library": "1.5.0", "Nancy": "2.0.0-barneyrubble", "System.IdentityModel.Tokens.Jwt": "5.0.0-rc2-305061149", "System.Security.Principal": "4.0.1", "System.Security.Claims": "4.0.1", "System.Globalization.Extensions": "4.0.1" }, "frameworks": { "netstandard1.5": { "imports": "dnxcore50" } }, "tooling": { "defaultNamespace": "MicroserivceNET.Auth" } } ================================================ FILE: src/Chapter11/MicroserviceNET.Logging/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter11/MicroserviceNET.Logging/Application_Packages/LibOwin.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. // Copyright (c) Damian Hickey. All rights reserved. See License.txt in the project root for license information. // https://github.com/damianh/LibOwin // Modifying this file may result in difficulties when upgrading the package. // All types are internal. Add a LIBOWIN_PUBLIC compilation symbol to make them public. namespace LibOwin.Infrastructure { using System; using System.Collections; using System.Collections.Generic; using System.Linq; internal static partial class Constants { internal const string Https = "HTTPS"; internal const string HttpDateFormat = "r"; internal static partial class Headers { internal const string ContentType = "Content-Type"; internal const string CacheControl = "Cache-Control"; internal const string MediaType = "Media-Type"; internal const string Accept = "Accept"; internal const string Host = "Host"; internal const string ETag = "ETag"; internal const string Location = "Location"; internal const string ContentLength = "Content-Length"; internal const string SetCookie = "Set-Cookie"; internal const string Expires = "Expires"; } } internal struct HeaderSegment : IEquatable { private readonly StringSegment _formatting; private readonly StringSegment _data; // // Initializes a new instance of the class. // public HeaderSegment(StringSegment formatting, StringSegment data) { _formatting = formatting; _data = data; } public StringSegment Formatting { get { return _formatting; } } public StringSegment Data { get { return _data; } } #region Equality members public bool Equals(HeaderSegment other) { return _formatting.Equals(other._formatting) && _data.Equals(other._data); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegment && Equals((HeaderSegment)obj); } public override int GetHashCode() { unchecked { return (_formatting.GetHashCode() * 397) ^ _data.GetHashCode(); } } public static bool operator ==(HeaderSegment left, HeaderSegment right) { return left.Equals(right); } public static bool operator !=(HeaderSegment left, HeaderSegment right) { return !left.Equals(right); } #endregion } internal struct HeaderSegmentCollection : IEnumerable, IEquatable { private readonly string[] _headers; public HeaderSegmentCollection(string[] headers) { _headers = headers; } #region Equality members public bool Equals(HeaderSegmentCollection other) { return Equals(_headers, other._headers); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegmentCollection && Equals((HeaderSegmentCollection)obj); } public override int GetHashCode() { return (_headers != null ? _headers.GetHashCode() : 0); } public static bool operator ==(HeaderSegmentCollection left, HeaderSegmentCollection right) { return left.Equals(right); } public static bool operator !=(HeaderSegmentCollection left, HeaderSegmentCollection right) { return !left.Equals(right); } #endregion public Enumerator GetEnumerator() { return new Enumerator(_headers); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } internal struct Enumerator : IEnumerator { private readonly string[] _headers; private int _index; private string _header; private int _headerLength; private int _offset; private int _leadingStart; private int _leadingEnd; private int _valueStart; private int _valueEnd; private int _trailingStart; private Mode _mode; private static readonly string[] NoHeaders = new string[0]; public Enumerator(string[] headers) { _headers = headers ?? NoHeaders; _header = string.Empty; _headerLength = -1; _index = -1; _offset = -1; _leadingStart = -1; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; _mode = Mode.Leading; } private enum Mode { Leading, Value, ValueQuoted, Trailing, Produce, } private enum Attr { Value, Quote, Delimiter, Whitespace } public HeaderSegment Current { get { return new HeaderSegment( new StringSegment(_header, _leadingStart, _leadingEnd - _leadingStart), new StringSegment(_header, _valueStart, _valueEnd - _valueStart)); } } object IEnumerator.Current { get { return Current; } } public void Dispose() { } public bool MoveNext() { while (true) { if (_mode == Mode.Produce) { _leadingStart = _trailingStart; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; if (_offset == _headerLength && _leadingStart != -1 && _leadingStart != _offset) { // Also produce trailing whitespace _leadingEnd = _offset; return true; } _mode = Mode.Leading; } // if end of a string if (_offset == _headerLength) { ++_index; _offset = -1; _leadingStart = 0; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; // if that was the last string if (_index == _headers.Length) { // no more move nexts return false; } // grab the next string _header = _headers[_index] ?? string.Empty; _headerLength = _header.Length; } while (true) { ++_offset; char ch = _offset == _headerLength ? (char)0 : _header[_offset]; // todo - array of attrs Attr attr = char.IsWhiteSpace(ch) ? Attr.Whitespace : ch == '\"' ? Attr.Quote : (ch == ',' || ch == (char)0) ? Attr.Delimiter : Attr.Value; switch (_mode) { case Mode.Leading: switch (attr) { case Attr.Delimiter: _leadingEnd = _offset; _mode = Mode.Produce; break; case Attr.Quote: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.ValueQuoted; break; case Attr.Value: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; case Mode.Value: switch (attr) { case Attr.Quote: _mode = Mode.ValueQuoted; break; case Attr.Delimiter: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; break; case Attr.Value: // more break; case Attr.Whitespace: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Trailing; break; } break; case Mode.ValueQuoted: switch (attr) { case Attr.Quote: _mode = Mode.Value; break; case Attr.Delimiter: if (ch == (char)0) { _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; } break; case Attr.Value: case Attr.Whitespace: // more break; } break; case Mode.Trailing: switch (attr) { case Attr.Delimiter: _mode = Mode.Produce; break; case Attr.Quote: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.ValueQuoted; break; case Attr.Value: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; } if (_mode == Mode.Produce) { return true; } } } } public void Reset() { _index = 0; _offset = 0; _leadingStart = 0; _leadingEnd = 0; _valueStart = 0; _valueEnd = 0; } } } internal struct StringSegment : IEquatable { private readonly string _buffer; private readonly int _offset; private readonly int _count; // // Initializes a new instance of the class. // public StringSegment(string buffer, int offset, int count) { _buffer = buffer; _offset = offset; _count = count; } public string Buffer { get { return _buffer; } } public int Offset { get { return _offset; } } public int Count { get { return _count; } } public string Value { get { return _offset == -1 ? null : _buffer.Substring(_offset, _count); } } public bool HasValue { get { return _offset != -1 && _count != 0 && _buffer != null; } } #region Equality members public bool Equals(StringSegment other) { return string.Equals(_buffer, other._buffer) && _offset == other._offset && _count == other._count; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is StringSegment && Equals((StringSegment)obj); } public override int GetHashCode() { unchecked { int hashCode = (_buffer != null ? _buffer.GetHashCode() : 0); hashCode = (hashCode * 397) ^ _offset; hashCode = (hashCode * 397) ^ _count; return hashCode; } } public static bool operator ==(StringSegment left, StringSegment right) { return left.Equals(right); } public static bool operator !=(StringSegment left, StringSegment right) { return !left.Equals(right); } #endregion public bool StartsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public bool EndsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset + _count - textLength, text, 0, textLength, comparisonType) == 0; } public bool Equals(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count != textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public string Substring(int offset, int length) { return _buffer.Substring(_offset + offset, length); } public StringSegment Subsegment(int offset, int length) { return new StringSegment(_buffer, _offset + offset, length); } public override string ToString() { return Value ?? string.Empty; } } internal static partial class OwinHelpers { private static readonly Action AddCookieCallback = (name, value, state) => { var dictionary = (IDictionary)state; if (!dictionary.ContainsKey(name)) { dictionary.Add(name, value); } }; private static readonly char[] SemicolonAndComma = new[] { ';', ',' }; internal static IDictionary GetCookies(IOwinRequest request) { var cookies = request.Get>("Microsoft.Owin.Cookies#dictionary"); if (cookies == null) { cookies = new Dictionary(StringComparer.Ordinal); request.Set("Microsoft.Owin.Cookies#dictionary", cookies); } string text = GetHeader(request.Headers, "Cookie"); if (request.Get("Microsoft.Owin.Cookies#text") != text) { cookies.Clear(); ParseDelimited(text, SemicolonAndComma, AddCookieCallback, cookies); request.Set("Microsoft.Owin.Cookies#text", text); } return cookies; } internal static void ParseDelimited(string text, char[] delimiters, Action callback, object state) { int textLength = text.Length; int equalIndex = text.IndexOf('='); if (equalIndex == -1) { equalIndex = textLength; } int scanIndex = 0; while (scanIndex < textLength) { int delimiterIndex = text.IndexOfAny(delimiters, scanIndex); if (delimiterIndex == -1) { delimiterIndex = textLength; } if (equalIndex < delimiterIndex) { while (scanIndex != equalIndex && char.IsWhiteSpace(text[scanIndex])) { ++scanIndex; } string name = text.Substring(scanIndex, equalIndex - scanIndex); string value = text.Substring(equalIndex + 1, delimiterIndex - equalIndex - 1); callback( Uri.UnescapeDataString(name.Replace('+', ' ')), Uri.UnescapeDataString(value.Replace('+', ' ')), state); equalIndex = text.IndexOf('=', delimiterIndex); if (equalIndex == -1) { equalIndex = textLength; } } scanIndex = delimiterIndex + 1; } } } internal static partial class OwinHelpers { public static string GetHeader(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : string.Join(",", values); } public static IEnumerable GetHeaderSplit(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : GetHeaderSplitImplementation(values); } private static IEnumerable GetHeaderSplitImplementation(string[] values) { foreach (var segment in new HeaderSegmentCollection(values)) { if (segment.Data.HasValue) { yield return DeQuote(segment.Data.Value); } } } public static string[] GetHeaderUnmodified(IDictionary headers, string key) { if (headers == null) { throw new ArgumentNullException("headers"); } string[] values; return headers.TryGetValue(key, out values) ? values : null; } public static void SetHeader(IDictionary headers, string key, string value) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (string.IsNullOrWhiteSpace(value)) { headers.Remove(key); } else { headers[key] = new[] { value }; } } public static void SetHeaderJoined(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = new[] { string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } // Quote items that contain comas and are not already quoted. private static string QuoteIfNeeded(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Contains(',')) { if (value[0] != '"' || value[value.Length - 1] != '"') { value = '"' + value + '"'; } } return value; } private static string DeQuote(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"') { value = value.Substring(1, value.Length - 2); } return value; } public static void SetHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = values; } } public static void SetHeaderUnmodified(IDictionary headers, string key, IEnumerable values) { if (headers == null) { throw new ArgumentNullException("headers"); } headers[key] = values.ToArray(); } public static void AppendHeader(IDictionary headers, string key, string values) { if (string.IsNullOrWhiteSpace(values)) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeader(headers, key, values); } else { headers[key] = new[] { existing + "," + values }; } } public static void AppendHeaderJoined(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeaderJoined(headers, key, values); } else { headers[key] = new[] { existing + "," + string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } public static void AppendHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string[] existing = GetHeaderUnmodified(headers, key); if (existing == null) { SetHeaderUnmodified(headers, key, values); } else { SetHeaderUnmodified(headers, key, existing.Concat(values)); } } } internal static partial class OwinHelpers { private static readonly Action AppendItemCallback = (name, value, state) => { var dictionary = (IDictionary>)state; List existing; if (!dictionary.TryGetValue(name, out existing)) { dictionary.Add(name, new List(1) { value }); } else { existing.Add(value); } }; private static readonly char[] AmpersandAndSemicolon = new[] { '&', ';' }; internal static IDictionary GetQuery(IOwinRequest request) { var query = request.Get>("Microsoft.Owin.Query#dictionary"); if (query == null) { query = new Dictionary(StringComparer.OrdinalIgnoreCase); request.Set("Microsoft.Owin.Query#dictionary", query); } string text = request.QueryString.Value; if (request.Get("Microsoft.Owin.Query#text") != text) { query.Clear(); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, AmpersandAndSemicolon, AppendItemCallback, accumulator); foreach (var kv in accumulator) { query.Add(kv.Key, kv.Value.ToArray()); } request.Set("Microsoft.Owin.Query#text", text); } return query; } internal static IFormCollection GetForm(string text) { IDictionary form = new Dictionary(StringComparer.OrdinalIgnoreCase); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, new[] { '&' }, AppendItemCallback, accumulator); foreach (var kv in accumulator) { form.Add(kv.Key, kv.Value.ToArray()); } return new FormCollection(form); } internal static string GetJoinedValue(IDictionary store, string key) { string[] values = GetUnmodifiedValues(store, key); return values == null ? null : string.Join(",", values); } internal static string[] GetUnmodifiedValues(IDictionary store, string key) { if (store == null) { throw new ArgumentNullException("store"); } string[] values; return store.TryGetValue(key, out values) ? values : null; } } internal static partial class OwinHelpers { internal static string GetHost(IOwinRequest request) { IHeaderDictionary headers = request.Headers; string host = GetHeader(headers, "Host"); if (!string.IsNullOrWhiteSpace(host)) { return host; } string localIpAddress = request.LocalIpAddress ?? "localhost"; var localPort = request.Get(OwinConstants.CommonKeys.LocalPort); return string.IsNullOrWhiteSpace(localPort) ? localIpAddress : (localIpAddress + ":" + localPort); } } } namespace LibOwin { using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Text; using System.Threading; using System.Threading.Tasks; using LibOwin.Infrastructure; #if LIBOWIN_PUBLIC public partial class CookieOptions { } public partial class FormCollection { } public partial class HeaderDictionary { } public partial struct HostString { } public partial interface IFormCollection { } public partial interface IHeaderDictionary { } public partial interface IOwinContext { } public partial interface IOwinRequest { } public partial interface IOwinResponse { } public partial interface IReadableStringCollection { } public partial class OwinContext { } public partial class OwinRequest { } public partial class OwinResponse { } public partial struct PathString { } public partial struct QueryString { } public partial class ReadableStringCollection { } public partial class RequestCookieCollection { } public partial class ResponseCookieCollection { } public partial class IOwinResponseExtension { } #endif /// /// Options used to create a new cookie. /// partial class CookieOptions { /// /// Creates a default cookie with a path of '/'. /// public CookieOptions() { Path = "/"; } /// /// Gets or sets the domain to associate the cookie with. /// /// The domain to associate the cookie with. public string Domain { get; set; } /// /// Gets or sets the cookie path. /// /// The cookie path. public string Path { get; set; } /// /// Gets or sets the expiration date and time for the cookie. /// /// The expiration date and time for the cookie. public DateTime? Expires { get; set; } /// /// Gets or sets a value that indicates whether to transmit the cookie using Secure Sockets Layer (SSL)—that is, over HTTPS only. /// /// true to transmit the cookie only over an SSL connection (HTTPS); otherwise, false. public bool Secure { get; set; } /// /// Gets or sets a value that indicates whether a cookie is accessible by client-side script. /// /// true if a cookie is accessible by client-side script; otherwise, false. public bool HttpOnly { get; set; } } /// /// Contains the parsed form values. /// partial class FormCollection : ReadableStringCollection, IFormCollection { /// /// Initializes a new instance of the class. /// /// The store for the form. public FormCollection(IDictionary store) : base(store) {} } /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial class HeaderDictionary : IHeaderDictionary { /// /// Initializes a new instance of the class. /// /// The underlying data store. public HeaderDictionary(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Gets an that contains the keys in the ;. /// /// An that contains the keys in the . public ICollection Keys { get { return Store.Keys; } } /// /// /// public ICollection Values { get { return Store.Values; } } /// /// Gets the number of elements contained in the ;. /// /// The number of elements contained in the . public int Count { get { return Store.Count; } } /// /// Gets a value that indicates whether the is in read-only mode. /// /// true if the is in read-only mode; otherwise, false. public bool IsReadOnly { get { return Store.IsReadOnly; } } /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string this[string key] { get { return Get(key); } set { Set(key, value); } } /// /// Throws KeyNotFoundException if the key is not present. /// /// The header name. /// string[] IDictionary.this[string key] { get { return Store[key]; } set { Store[key] = value; } } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Get the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string Get(string key) { return OwinHelpers.GetHeader(Store, key); } /// /// Get the associated values from the collection without modification. /// /// The header name. /// the associated value from the collection without modification, or null if the key is not present. public IList GetValues(string key) { return OwinHelpers.GetHeaderUnmodified(Store, key); } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. public IList GetCommaSeparatedValues(string key) { IEnumerable values = OwinHelpers.GetHeaderSplit(Store, key); return values == null ? null : values.ToList(); } /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. public void Append(string key, string value) { OwinHelpers.AppendHeader(Store, key, value); } /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. public void AppendValues(string key, params string[] values) { OwinHelpers.AppendHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. public void AppendCommaSeparatedValues(string key, params string[] values) { OwinHelpers.AppendHeaderJoined(Store, key, values); } /// /// Sets a specific header value. /// /// The header name. /// The header value. public void Set(string key, string value) { OwinHelpers.SetHeader(Store, key, value); } /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. public void SetValues(string key, params string[] values) { OwinHelpers.SetHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. public void SetCommaSeparatedValues(string key, params string[] values) { OwinHelpers.SetHeaderJoined(Store, key, values); } /// /// Adds the given header and values to the collection. /// /// The header name. /// The header values. public void Add(string key, string[] value) { Store.Add(key, value); } /// /// Determines whether the contains a specific key. /// /// The key. /// true if the contains a specific key; otherwise, false. public bool ContainsKey(string key) { return Store.ContainsKey(key); } /// /// Removes the given header from the collection. /// /// The header name. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(string key) { return Store.Remove(key); } /// /// Retrieves a value from the dictionary. /// /// The header name. /// The value. /// true if the contains the key; otherwise, false. public bool TryGetValue(string key, out string[] value) { return Store.TryGetValue(key, out value); } /// /// Adds a new list of items to the collection. /// /// The item to add. public void Add(KeyValuePair item) { Store.Add(item); } /// /// Clears the entire list of objects. /// public void Clear() { Store.Clear(); } /// /// Returns a value indicating whether the specified object occurs within this collection. /// /// The item. /// true if the specified object occurs within this collection; otherwise, false. public bool Contains(KeyValuePair item) { return Store.Contains(item); } /// /// Copies the elements to a one-dimensional Array instance at the specified index. /// /// The one-dimensional Array that is the destination of the specified objects copied from the . /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) { Store.CopyTo(array, arrayIndex); } /// /// Removes the given item from the the collection. /// /// The item. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(KeyValuePair item) { return Store.Remove(item); } } /// /// Represents the host portion of a Uri can be used to construct Uri's properly formatted and encoded for use in /// HTTP headers. /// partial struct HostString : IEquatable { private readonly string _value; /// /// Creates a new HostString without modification. The value should be Unicode rather than punycode, and may have a port. /// IPv4 and IPv6 addresses are also allowed, and also may have ports. /// /// public HostString(string value) { _value = value; } /// /// Returns the original value from the constructor. /// public string Value { get { return _value; } } /// /// Returns the value as normalized by ToUriComponent(). /// /// public override string ToString() { return ToUriComponent(); } /// /// Returns the value properly formatted and encoded for use in a URI in a HTTP header. /// Any Unicode is converted to punycode. IPv6 addresses will have brackets added if they are missing. /// /// public string ToUriComponent() { int index; if (string.IsNullOrEmpty(_value)) { return string.Empty; } else if (_value.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port return _value; } else if ((index = _value.IndexOf(':')) >= 0 && index < _value.Length - 1 && _value.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons return "[" + _value + "]"; } else if (index >= 0) { // Has a port string port = _value.Substring(index); var mapping = new IdnMapping(); return mapping.GetAscii(_value, 0, index) + port; } else { var mapping = new IdnMapping(); return mapping.GetAscii(_value); } } /// /// Creates a new HostString from the given uri component. /// Any punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(string uriComponent) { if (!string.IsNullOrEmpty(uriComponent)) { int index; if (uriComponent.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port } else if ((index = uriComponent.IndexOf(':')) >= 0 && index < uriComponent.Length - 1 && uriComponent.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons } else if (uriComponent.IndexOf("xn--", StringComparison.Ordinal) >= 0) { // Contains punycode if (index >= 0) { // Has a port string port = uriComponent.Substring(index); IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent, 0, index) + port; } else { IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent); } } } return new HostString(uriComponent); } /// /// Creates a new HostString from the host and port of the give Uri instance. /// Punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new HostString(uri.GetComponents( UriComponents.NormalizedHost | // Always convert punycode to Unicode. UriComponents.HostAndPort, UriFormat.Unescaped)); } /// /// Compares the equality of the Value property, ignoring case. /// /// /// public bool Equals(HostString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares against the given object only if it is a HostString. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HostString && Equals((HostString)obj); } /// /// Gets a hash code for the value. /// /// public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(HostString left, HostString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(HostString left, HostString right) { return !left.Equals(right); } } /// /// Contains the parsed form values. /// partial interface IFormCollection : IReadableStringCollection {} /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial interface IHeaderDictionary : IReadableStringCollection, IDictionary { /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. new string this[string key] { get; set; } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. IList GetCommaSeparatedValues(string key); /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. void Append(string key, string value); /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. void AppendValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. void AppendCommaSeparatedValues(string key, params string[] values); /// /// Sets a specific header value. /// /// The header name. /// The header value. void Set(string key, string value); /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. void SetValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. void SetCommaSeparatedValues(string key, params string[] values); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinContext { /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. IOwinRequest Request { get; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. IOwinResponse Response { get; } /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. TextWriter TraceOutput { get; set; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinContext Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinRequest { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or set the HTTP method. /// /// The HTTP method. string Method { get; set; } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. string Scheme { get; set; } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. bool IsSecure { get; } /// /// Gets or set the Host header. May include the port. /// /// The Host header. HostString Host { get; set; } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. PathString PathBase { get; set; } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. PathString Path { get; set; } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. QueryString QueryString { get; set; } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. IReadableStringCollection Query { get; } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. Uri Uri { get; } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. string Protocol { get; set; } /// /// Gets the request headers. /// /// The request headers. IHeaderDictionary Headers { get; } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. RequestCookieCollection Cookies { get; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. string CacheControl { get; set; } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. string MediaType { get; set; } /// /// Gets or set the Accept header. /// /// The Accept header. string Accept { get; set; } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. Stream Body { get; set; } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. CancellationToken CallCancelled { get; set; } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. string LocalIpAddress { get; set; } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. int? LocalPort { get; set; } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. string RemoteIpAddress { get; set; } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. int? RemotePort { get; set; } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. ClaimsPrincipal User { get; set; } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. Task ReadFormAsync(); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinRequest Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinResponse { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. int StatusCode { get; set; } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. string ReasonPhrase { get; set; } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. string Protocol { get; set; } /// /// Gets the response header collection. /// /// The response header collection. IHeaderDictionary Headers { get; } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. ResponseCookieCollection Cookies { get; } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. long? ContentLength { get; set; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Expires header. /// /// The Expires header. DateTimeOffset? Expires { get; set; } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. string ETag { get; set; } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. Stream Body { get; set; } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. void OnSendingHeaders(Action callback, object state); /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. void Redirect(string location); /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. void Write(string text); /// /// Writes the given bytes to the response body stream. /// /// The response data. void Write(byte[] data); /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. void Write(byte[] data, int offset, int count); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(string text); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(string text, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, int offset, int count, CancellationToken token); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinResponse Set(string key, T value); } /// /// Accessors for headers, query, forms, etc. /// partial interface IReadableStringCollection : IEnumerable> { /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string this[string key] { get; } // Joined /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string Get(string key); // Joined /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// IList GetValues(string key); // Raw } internal static class OwinConstants { #region OWIN v1.0.0 - 3.2.1. Request Data // http://owin.org/spec/owin-1.0.0.html public const string RequestScheme = "owin.RequestScheme"; public const string RequestMethod = "owin.RequestMethod"; public const string RequestPathBase = "owin.RequestPathBase"; public const string RequestPath = "owin.RequestPath"; public const string RequestQueryString = "owin.RequestQueryString"; public const string RequestProtocol = "owin.RequestProtocol"; public const string RequestHeaders = "owin.RequestHeaders"; public const string RequestBody = "owin.RequestBody"; public const string RequestUser = "owin.RequestUser"; //owin 1.0.1 #endregion #region OWIN v1.0.0 - 3.2.2. Response Data // http://owin.org/spec/owin-1.0.0.html public const string ResponseStatusCode = "owin.ResponseStatusCode"; public const string ResponseReasonPhrase = "owin.ResponseReasonPhrase"; public const string ResponseProtocol = "owin.ResponseProtocol"; public const string ResponseHeaders = "owin.ResponseHeaders"; public const string ResponseBody = "owin.ResponseBody"; #endregion #region OWIN v1.0.0 - 3.2.3. Other Data // http://owin.org/spec/owin-1.0.0.html public const string CallCancelled = "owin.CallCancelled"; public const string OwinVersion = "owin.Version"; #endregion #region OWIN Keys for IAppBuilder.Properties internal static class Builder { public const string AddSignatureConversion = "builder.AddSignatureConversion"; public const string DefaultApp = "builder.DefaultApp"; } #endregion #region OWIN Key Guidelines and Common Keys - 6. Common keys // http://owin.org/spec/CommonKeys.html internal static class CommonKeys { public const string ClientCertificate = "ssl.ClientCertificate"; public const string RemoteIpAddress = "server.RemoteIpAddress"; public const string RemotePort = "server.RemotePort"; public const string LocalIpAddress = "server.LocalIpAddress"; public const string LocalPort = "server.LocalPort"; public const string IsLocal = "server.IsLocal"; public const string TraceOutput = "host.TraceOutput"; public const string Addresses = "host.Addresses"; public const string AppName = "host.AppName"; public const string Capabilities = "server.Capabilities"; public const string OnSendingHeaders = "server.OnSendingHeaders"; public const string OnAppDisposing = "host.OnAppDisposing"; public const string Scheme = "scheme"; public const string Host = "host"; public const string Port = "port"; public const string Path = "path"; } #endregion #region SendFiles v0.3.0 // http://owin.org/extensions/owin-SendFile-Extension-v0.3.0.htm internal static class SendFiles { // 3.1. Startup public const string Version = "sendfile.Version"; public const string Support = "sendfile.Support"; public const string Concurrency = "sendfile.Concurrency"; // 3.2. Per Request public const string SendAsync = "sendfile.SendAsync"; } #endregion #region Opaque v0.3.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class OpaqueConstants { // 3.1. Startup public const string Version = "opaque.Version"; // 3.2. Per Request public const string Upgrade = "opaque.Upgrade"; // 5. Consumption public const string Stream = "opaque.Stream"; // public const string Version = "opaque.Version"; // redundant, declared above public const string CallCancelled = "opaque.CallCancelled"; } #endregion #region WebSocket v0.4.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class WebSocket { // 3.1. Startup public const string Version = "websocket.Version"; // 3.2. Per Request public const string Accept = "websocket.Accept"; // 4. Accept public const string SubProtocol = "websocket.SubProtocol"; // 5. Consumption public const string SendAsync = "websocket.SendAsync"; public const string ReceiveAsync = "websocket.ReceiveAsync"; public const string CloseAsync = "websocket.CloseAsync"; // public const string Version = "websocket.Version"; // redundant, declared above public const string CallCancelled = "websocket.CallCancelled"; public const string ClientCloseStatus = "websocket.ClientCloseStatus"; public const string ClientCloseDescription = "websocket.ClientCloseDescription"; } #endregion #region Security v0.1.0 // http://owin.org/extensions/owin-Security-Extension-v0.1.0.htm internal static class Security { // 3.2. Per Request public const string User = "server.User"; public const string Authenticate = "security.Authenticate"; // 3.3. Response public const string SignIn = "security.SignIn"; public const string SignOut = "security.SignOut"; public const string SignOutProperties = "security.SignOutProperties"; public const string Challenge = "security.Challenge"; } #endregion } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinContext : IOwinContext { /// /// Create a new context with only request and response header collections. /// public OwinContext() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Create a new wrapper. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinContext(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. public virtual IOwinRequest Request { get; private set; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. public virtual IOwinResponse Response { get; private set; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. public virtual TextWriter TraceOutput { get { return Get(OwinConstants.CommonKeys.TraceOutput); } set { Set(OwinConstants.CommonKeys.TraceOutput, value); } } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinContext Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinRequest : IOwinRequest { /// /// Create a new context with only request and response header collections. /// public OwinRequest() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Create a new environment wrapper exposing request properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinRequest(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or set the HTTP method. /// /// The HTTP method. public virtual string Method { get { return Get(OwinConstants.RequestMethod); } set { Set(OwinConstants.RequestMethod, value); } } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. public virtual string Scheme { get { return Get(OwinConstants.RequestScheme); } set { Set(OwinConstants.RequestScheme, value); } } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. public virtual bool IsSecure { get { return string.Equals(Scheme, Constants.Https, StringComparison.OrdinalIgnoreCase); } } /// /// Gets or set the Host header. May include the port. /// /// The Host header. public virtual HostString Host { get { return new HostString(OwinHelpers.GetHost(this)); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Host, value.Value); } } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. public virtual PathString PathBase { get { return new PathString(Get(OwinConstants.RequestPathBase)); } set { Set(OwinConstants.RequestPathBase, value.Value); } } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. public virtual PathString Path { get { return new PathString(Get(OwinConstants.RequestPath)); } set { Set(OwinConstants.RequestPath, value.Value); } } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. public virtual QueryString QueryString { get { return new QueryString(Get(OwinConstants.RequestQueryString)); } set { Set(OwinConstants.RequestQueryString, value.Value); } } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. public virtual IReadableStringCollection Query { get { return new ReadableStringCollection(OwinHelpers.GetQuery(this)); } } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. public virtual Uri Uri { get { return new Uri(Scheme + "://" + Host + PathBase + Path + QueryString); } } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. public virtual string Protocol { get { return Get(OwinConstants.RequestProtocol); } set { Set(OwinConstants.RequestProtocol, value); } } /// /// Gets the request headers. /// /// The request headers. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.RequestHeaders); } } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. public RequestCookieCollection Cookies { get { return new RequestCookieCollection(OwinHelpers.GetCookies(this)); } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. public virtual string CacheControl { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.CacheControl); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.CacheControl, value); } } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. public virtual string MediaType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.MediaType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.MediaType, value); } } /// /// Gets or set the Accept header. /// /// The Accept header. public virtual string Accept { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Accept); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Accept, value); } } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. public virtual Stream Body { get { return Get(OwinConstants.RequestBody); } set { Set(OwinConstants.RequestBody, value); } } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. public virtual CancellationToken CallCancelled { get { return Get(OwinConstants.CallCancelled); } set { Set(OwinConstants.CallCancelled, value); } } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. public virtual string LocalIpAddress { get { return Get(OwinConstants.CommonKeys.LocalIpAddress); } set { Set(OwinConstants.CommonKeys.LocalIpAddress, value); } } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. public virtual int? LocalPort { get { int value; if (int.TryParse(LocalPortString, out value)) { return value; } return null; } set { if (value.HasValue) { LocalPortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.LocalPort); } } } private string LocalPortString { get { return Get(OwinConstants.CommonKeys.LocalPort); } set { Set(OwinConstants.CommonKeys.LocalPort, value); } } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. public virtual string RemoteIpAddress { get { return Get(OwinConstants.CommonKeys.RemoteIpAddress); } set { Set(OwinConstants.CommonKeys.RemoteIpAddress, value); } } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. public virtual int? RemotePort { get { int value; if (int.TryParse(RemotePortString, out value)) { return value; } return null; } set { if (value.HasValue) { RemotePortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.RemotePort); } } } private string RemotePortString { get { return Get(OwinConstants.CommonKeys.RemotePort); } set { Set(OwinConstants.CommonKeys.RemotePort, value); } } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. public virtual ClaimsPrincipal User { get { var claimsPrincipal = Get(OwinConstants.RequestUser); return claimsPrincipal ?? Get(OwinConstants.Security.User) as ClaimsPrincipal; } set { Set(OwinConstants.RequestUser, value); } } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. public async Task ReadFormAsync() { var form = Get("Microsoft.Owin.Form#collection"); if (form == null) { string text; // Don't close, it prevents re-winding. using (var reader = new StreamReader(Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 4 * 1024, leaveOpen: true)) { text = await reader.ReadToEndAsync(); } form = OwinHelpers.GetForm(text); Set("Microsoft.Owin.Form#collection", form); } return form; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinRequest Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinResponse : IOwinResponse { /// /// Create a new context with only request and response header collections. /// public OwinResponse() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Creates a new environment wrapper exposing response properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinResponse(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. public virtual int StatusCode { get { return Get(OwinConstants.ResponseStatusCode, 200); } set { Set(OwinConstants.ResponseStatusCode, value); } } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. public virtual string ReasonPhrase { get { return Get(OwinConstants.ResponseReasonPhrase); } set { Set(OwinConstants.ResponseReasonPhrase, value); } } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. public virtual string Protocol { get { return Get(OwinConstants.ResponseProtocol); } set { Set(OwinConstants.ResponseProtocol, value); } } /// /// Gets the response header collection. /// /// The response header collection. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.ResponseHeaders); } } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. public virtual ResponseCookieCollection Cookies { get { return new ResponseCookieCollection(Headers); } } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. public virtual long? ContentLength { get { long value; if (long.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentLength), out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentLength, value.Value.ToString(CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.ContentLength); } } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Expires header. /// /// The Expires header. public virtual DateTimeOffset? Expires { get { DateTimeOffset value; if (DateTimeOffset.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Expires), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Expires, value.Value.ToString(Constants.HttpDateFormat, CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.Expires); } } } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. public virtual string ETag { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ETag); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ETag, value); } } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. public virtual Stream Body { get { return Get(OwinConstants.ResponseBody); } set { Set(OwinConstants.ResponseBody, value); } } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. public virtual void OnSendingHeaders(Action callback, object state) { var onSendingHeaders = Get, object>>(OwinConstants.CommonKeys.OnSendingHeaders); if (onSendingHeaders == null) { throw new NotSupportedException(Resources.Exception_MissingOnSendingHeaders); } onSendingHeaders(callback, state); } /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. public virtual void Redirect(string location) { StatusCode = 302; OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Location, location); } /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. public virtual void Write(string text) { Write(Encoding.UTF8.GetBytes(text)); } /// /// Writes the given bytes to the response body stream. /// /// The response data. public virtual void Write(byte[] data) { Write(data, 0, data == null ? 0 : data.Length); } /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. public virtual void Write(byte[] data, int offset, int count) { Body.Write(data, offset, count); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text) { return WriteAsync(text, CancellationToken.None); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text, CancellationToken token) { return WriteAsync(Encoding.UTF8.GetBytes(text), token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data) { return WriteAsync(data, CancellationToken.None); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, CancellationToken token) { return WriteAsync(data, 0, data == null ? 0 : data.Length, token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, int offset, int count, CancellationToken token) { return Body.WriteAsync(data, offset, count, token); } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { return Get(key, default(T)); } private T Get(string key, T fallback) { object value; return Environment.TryGetValue(key, out value) ? (T)value : fallback; } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinResponse Set(string key, T value) { Environment[key] = value; return this; } } /// /// Provides correct escaping for Path and PathBase values when needed to reconstruct a request or redirect URI string /// partial struct PathString : IEquatable { private static readonly Func EscapeDataString = Uri.EscapeDataString; /// /// Represents the empty path. This field is read-only. /// public static readonly PathString Empty = new PathString(String.Empty); private readonly string _value; /// /// Initialize the path string with a given value. This value must be in un-escaped format. Use /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format. /// /// The unescaped path to be assigned to the Value property. public PathString(string value) { if (!String.IsNullOrEmpty(value) && value[0] != '/') { throw new ArgumentException(Resources.Exception_PathMustStartWithSlash, "value"); } _value = value; } /// /// The unescaped path value /// public string Value { get { return _value; } } /// /// True if the path is not empty /// public bool HasValue { get { return !String.IsNullOrEmpty(_value); } } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public override string ToString() { return ToUriComponent(); } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public string ToUriComponent() { if (HasValue) { if (RequiresEscaping(_value)) { // TODO: Measure the cost of this escaping and consider optimizing. return String.Join("/", _value.Split('/').Select(EscapeDataString)); } return _value; } return String.Empty; } // Very conservative, these characters do not need to be escaped in a path. private static bool RequiresEscaping(string value) { for (int i = 0; i < value.Length; i++) { char c = value[i]; // Check conservatively for safe characters. See http://www.ietf.org/rfc/rfc3986.txt bool safeChar = (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '/' || c == '-' || c == '_'); if (!safeChar) { return true; } } return false; } /// /// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a path. /// /// The escaped path as it appears in the URI format. /// The resulting PathString public static PathString FromUriComponent(string uriComponent) { // REVIEW: what is the exactly correct thing to do? return new PathString(Uri.UnescapeDataString(uriComponent)); } /// /// Returns an PathString given the path as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting PathString public static PathString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } // REVIEW: what is the exactly correct thing to do? return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)); } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// public bool StartsWithSegments(PathString other) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { return value1.Length == value2.Length || value1[value2.Length] == '/'; } return false; } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// Any remaining segments from this instance not included in the other instance. /// public bool StartsWithSegments(PathString other, out PathString remaining) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { if (value1.Length == value2.Length || value1[value2.Length] == '/') { remaining = new PathString(value1.Substring(value2.Length)); return true; } } remaining = Empty; return false; } /// /// Adds two PathString instances into a combined PathString value. /// /// The combined PathString value public PathString Add(PathString other) { return new PathString(Value + other.Value); } /// /// Combines a PathString and QueryString into the joined URI formatted string value. /// /// The joined URI formatted string value public string Add(QueryString other) { return ToUriComponent() + other.ToUriComponent(); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public bool Equals(PathString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares this PathString value to another value using a specific StringComparison type /// /// The second PathString for comparison /// The StringComparison type to use /// True if both PathString values are equal public bool Equals(PathString other, StringComparison comparisonType) { return string.Equals(_value, other._value, comparisonType); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is PathString && Equals((PathString)obj, StringComparison.OrdinalIgnoreCase); } /// /// Returns the hash code for the PathString value. The hash code is provided by the OrdinalIgnoreCase implementation. /// /// The hash code public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are equal public static bool operator ==(PathString left, PathString right) { return left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are not equal public static bool operator !=(PathString left, PathString right) { return !left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static PathString operator +(PathString left, PathString right) { return left.Add(right); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static string operator +(PathString left, QueryString right) { return left.Add(right); } } /// /// Provides correct handling for QueryString value when needed to reconstruct a request or redirect URI string /// partial struct QueryString : IEquatable { /// /// Represents the empty query string. This field is read-only. /// public static readonly QueryString Empty = new QueryString(String.Empty); private readonly string _value; /// /// Initalize the query string with a given value. This value must be in escaped and delimited format without /// a leading '?' character. /// /// The query string to be assigned to the Value property. public QueryString(string value) { _value = value; } /// /// Initialize a query string with a single given parameter name and value. The value is /// /// The unencoded parameter name /// The unencoded parameter value public QueryString(string name, string value) { _value = Uri.EscapeDataString(name) + '=' + Uri.EscapeDataString(value); } /// /// The unescaped query string without the leading '?' character /// public string Value { get { return _value; } } /// /// True if the query string is not empty /// public bool HasValue { get { return !String.IsNullOrWhiteSpace(_value); } } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentally /// dangerous are escaped. /// /// The query string value public override string ToString() { return ToUriComponent(); } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentially /// dangerous are escaped. /// /// The query string value public string ToUriComponent() { // Escape things properly so System.Uri doesn't mis-interpret the data. return HasValue ? "?" + _value.Replace("#", "%23") : String.Empty; } /// /// Returns an QueryString given the query as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a query. /// /// The escaped query as it appears in the URI format. /// The resulting QueryString public static QueryString FromUriComponent(string uriComponent) { if (String.IsNullOrEmpty(uriComponent)) { return new QueryString(string.Empty); } if (uriComponent[0] != '?') { throw new ArgumentException(Resources.Exception_QueryStringMustStartWithDelimiter, "uriComponent"); } return new QueryString(uriComponent.Substring(1)); } /// /// Returns an QueryString given the query as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting QueryString public static QueryString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new QueryString(uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped)); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public bool Equals(QueryString other) { return string.Equals(_value, other._value); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is QueryString && Equals((QueryString)obj); } /// /// Returns the hash code for this instance. /// /// public override int GetHashCode() { return (_value != null ? _value.GetHashCode() : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(QueryString left, QueryString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(QueryString left, QueryString right) { return !left.Equals(right); } } /// /// Accessors for query, forms, etc. /// partial class ReadableStringCollection : IReadableStringCollection { /// /// Create a new wrapper /// /// public ReadableStringCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string this[string key] { get { return Get(key); } } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string Get(string key) { return OwinHelpers.GetJoinedValue(Store, key); } /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// public IList GetValues(string key) { string[] values; Store.TryGetValue(key, out values); return values; } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } /// /// A wrapper for the request Cookie header /// partial class RequestCookieCollection : IEnumerable> { /// /// Create a new wrapper /// /// public RequestCookieCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Returns null rather than throwing KeyNotFoundException /// /// /// public string this[string key] { get { string value; Store.TryGetValue(key, out value); return value; } } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } internal static class Resources { internal const string Exception_MissingOnSendingHeaders = "The OWIN key 'server.OnSsoendingHeaders' is not available for this request."; internal const string Exception_PathMustStartWithSlash = "The path must start with a '/' followed by one or more characters."; internal const string Exception_QueryStringMustStartWithDelimiter = "The query string must start with a '?' unless null or empty."; } /// /// A wrapper for the response Set-Cookie header /// partial class ResponseCookieCollection { /// /// Create a new wrapper /// /// public ResponseCookieCollection(IHeaderDictionary headers) { if (headers == null) { throw new ArgumentNullException("headers"); } Headers = headers; } private IHeaderDictionary Headers { get; set; } /// /// Add a new cookie and value /// /// /// public void Append(string key, string value) { Headers.AppendValues(Constants.Headers.SetCookie, Uri.EscapeDataString(key) + "=" + Uri.EscapeDataString(value) + "; path=/"); } /// /// Add a new cookie /// /// /// /// public void Append(string key, string value, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); bool expiresHasValue = options.Expires.HasValue; string setCookieValue = string.Concat( Uri.EscapeDataString(key), "=", Uri.EscapeDataString(value ?? string.Empty), !domainHasValue ? null : "; domain=", !domainHasValue ? null : options.Domain, !pathHasValue ? null : "; path=", !pathHasValue ? null : options.Path, !expiresHasValue ? null : "; expires=", !expiresHasValue ? null : options.Expires.Value.ToString("ddd, dd-MMM-yyyy HH:mm:ss ", CultureInfo.InvariantCulture) + "GMT", !options.Secure ? null : "; secure", !options.HttpOnly ? null : "; HttpOnly"); Headers.AppendValues("Set-Cookie", setCookieValue); } /// /// Sets an expired cookie /// /// public void Delete(string key) { Func predicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); var deleteCookies = new[] { Uri.EscapeDataString(key) + "=; expires=Thu, 01-Jan-1970 00:00:00 GMT" }; IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues == null || existingValues.Count == 0) { Headers.SetValues(Constants.Headers.SetCookie, deleteCookies); } else { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !predicate(value)).Concat(deleteCookies).ToArray()); } } /// /// Sets an expired cookie /// /// /// public void Delete(string key, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); Func rejectPredicate; if (domainHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("domain=" + options.Domain, StringComparison.OrdinalIgnoreCase) != -1; } else if (pathHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("path=" + options.Path, StringComparison.OrdinalIgnoreCase) != -1; } else { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); } IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues != null) { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !rejectPredicate(value)).ToArray()); } Append(key, string.Empty, new CookieOptions { Path = options.Path, Domain = options.Domain, Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), }); } } static partial class IOwinResponseExtension { /// /// Registers for an event that fires when the response headers are sent. /// /// The owin response /// The callback method. /// The callback state. public static void OnSendingHeaders(this IOwinResponse response, Action callback, T state) { if (response == null) { throw new ArgumentNullException("response"); } Action innerCallback = innerState => callback((T)innerState); response.OnSendingHeaders(innerCallback, state); } } } ================================================ FILE: src/Chapter11/MicroserviceNET.Logging/BuildFuncExtensions.cs ================================================ namespace MicroserviceNET.Logging { using System; using System.Threading.Tasks; using Serilog; using BuildFunc = System.Action, System.Threading.Tasks.Task>, System.Func, System.Threading.Tasks.Task>>>; public static class BuildFuncExtensions { public static BuildFunc UseMonitoringAndLogging( this BuildFunc buildFunc, ILogger log, Func> healthCheck) { buildFunc(next => GlobalErrorLogging.Middleware(next, log)); buildFunc(next => CorrelationToken.Middleware(next)); buildFunc(next => RequestLogging.Middleware(next, log)); buildFunc(next => PerformanceLogging.Middleware(next, log)); buildFunc(next => new MonitoringMiddleware(next, healthCheck).Invoke); return buildFunc; } } } ================================================ FILE: src/Chapter11/MicroserviceNET.Logging/LoggingMiddleware.cs ================================================ namespace MicroserviceNET.Logging { using System; using System.Diagnostics; using LibOwin; using Serilog; using Serilog.Context; using AppFunc = System.Func, System.Threading.Tasks.Task>; public class RequestLogging { public static AppFunc Middleware(AppFunc next, ILogger log) { return async env => { var owinContext = new OwinContext(env); log.Information("Incoming request: {@Method}, {@Path}, {@Headers}", owinContext.Request.Method, owinContext.Request.Path, owinContext.Request.Headers); await next(env).ConfigureAwait(false); log.Information("Outgoing response: {@StatusCode}, {@Headers}", owinContext.Response.StatusCode, owinContext.Response.Headers); }; } } public class PerformanceLogging { public static AppFunc Middleware(AppFunc next, ILogger log) { return async env => { var stopWatch = new Stopwatch(); stopWatch.Start(); await next(env).ConfigureAwait(false); stopWatch.Stop(); var owinContext = new OwinContext(env); log.Information("Request: {@Method} {@Path} executed in {RequestTime:000} ms", owinContext.Request.Method, owinContext.Request.Path, stopWatch.ElapsedMilliseconds); }; } } public class CorrelationToken { public static AppFunc Middleware(AppFunc next) { return async env => { Guid correlationToken; var owinContext = new OwinContext(env); if (!(owinContext.Request.Headers["Correlation-Token"] != null && Guid.TryParse(owinContext.Request.Headers["Correlation-Token"], out correlationToken))) correlationToken = Guid.NewGuid(); owinContext.Set("correlationToken", correlationToken.ToString()); using (LogContext.PushProperty("CorrelationToken", correlationToken)) await next(env).ConfigureAwait(false); }; } } public class GlobalErrorLogging { public static AppFunc Middleware(AppFunc next, ILogger log) { return async env => { try { await next(env).ConfigureAwait(false); } catch (Exception ex) { log.Error(ex, "Unhandled exception"); } }; } } } ================================================ FILE: src/Chapter11/MicroserviceNET.Logging/MicroserviceNET.Logging.xproj ================================================  14.0.25123 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) f73df2ab-708b-4916-ac28-e95117466511 MicroserviceNET.Logging .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter11/MicroserviceNET.Logging/MonitoringMiddleware.cs ================================================ namespace MicroserviceNET.Logging { using System; using System.Collections.Generic; using System.Threading.Tasks; using LibOwin; using AppFunc = System.Func, System.Threading.Tasks.Task>; public class MonitoringMiddleware { private readonly AppFunc next; private readonly Func> healthCheck; private static readonly PathString monitorPath = new PathString("/_monitor"); private static readonly PathString monitorShallowPath = new PathString("/_monitor/shallow"); private static readonly PathString monitorDeepPath = new PathString("/_monitor/deep"); public MonitoringMiddleware(AppFunc next, Func> healthCheck) { this.next = next; this.healthCheck = healthCheck; } public Task Invoke(IDictionary env) { var context = new OwinContext(env); if (context.Request.Path.StartsWithSegments(monitorPath)) return HandleMonitorEndpoint(context); else return this.next(env); } private Task HandleMonitorEndpoint(OwinContext context) { if (context.Request.Path.StartsWithSegments(monitorShallowPath)) return ShallowEndpoint(context); else if (context.Request.Path.StartsWithSegments(monitorDeepPath)) return DeepEndpoint(context); return Task.FromResult(0); } private async Task DeepEndpoint(OwinContext context) { if (await this.healthCheck().ConfigureAwait(false)) context.Response.StatusCode = 204; else context.Response.StatusCode = 503; } private Task ShallowEndpoint(OwinContext context) { context.Response.StatusCode = 204; return Task.FromResult(0); } } } ================================================ FILE: src/Chapter11/MicroserviceNET.Logging/project.json ================================================ { "version": "1.0.0-*", "dependencies": { "NETStandard.Library": "1.5.0", "Serilog": "2.0.0-rc-600", "System.Security.Principal": "4.0.1", "System.Security.Claims": "4.0.1", "System.Globalization.Extensions": "4.0.1" }, "frameworks": { "netstandard1.5": { "imports": "dnxcore50", "dependencies": { } } }, "tooling": { "defaultNamespace": "MicroserviceNET.Logging" } } ================================================ FILE: src/Chapter11/MicroserviceNET.Platform/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter11/MicroserviceNET.Platform/Application_Packages/LibOwin.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. // Copyright (c) Damian Hickey. All rights reserved. See License.txt in the project root for license information. // https://github.com/damianh/LibOwin // Modifying this file may result in difficulties when upgrading the package. // All types are internal. Add a LIBOWIN_PUBLIC compilation symbol to make them public. namespace LibOwin.Infrastructure { using System; using System.Collections; using System.Collections.Generic; using System.Linq; internal static partial class Constants { internal const string Https = "HTTPS"; internal const string HttpDateFormat = "r"; internal static partial class Headers { internal const string ContentType = "Content-Type"; internal const string CacheControl = "Cache-Control"; internal const string MediaType = "Media-Type"; internal const string Accept = "Accept"; internal const string Host = "Host"; internal const string ETag = "ETag"; internal const string Location = "Location"; internal const string ContentLength = "Content-Length"; internal const string SetCookie = "Set-Cookie"; internal const string Expires = "Expires"; } } internal struct HeaderSegment : IEquatable { private readonly StringSegment _formatting; private readonly StringSegment _data; // // Initializes a new instance of the class. // public HeaderSegment(StringSegment formatting, StringSegment data) { _formatting = formatting; _data = data; } public StringSegment Formatting { get { return _formatting; } } public StringSegment Data { get { return _data; } } #region Equality members public bool Equals(HeaderSegment other) { return _formatting.Equals(other._formatting) && _data.Equals(other._data); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegment && Equals((HeaderSegment)obj); } public override int GetHashCode() { unchecked { return (_formatting.GetHashCode() * 397) ^ _data.GetHashCode(); } } public static bool operator ==(HeaderSegment left, HeaderSegment right) { return left.Equals(right); } public static bool operator !=(HeaderSegment left, HeaderSegment right) { return !left.Equals(right); } #endregion } internal struct HeaderSegmentCollection : IEnumerable, IEquatable { private readonly string[] _headers; public HeaderSegmentCollection(string[] headers) { _headers = headers; } #region Equality members public bool Equals(HeaderSegmentCollection other) { return Equals(_headers, other._headers); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegmentCollection && Equals((HeaderSegmentCollection)obj); } public override int GetHashCode() { return (_headers != null ? _headers.GetHashCode() : 0); } public static bool operator ==(HeaderSegmentCollection left, HeaderSegmentCollection right) { return left.Equals(right); } public static bool operator !=(HeaderSegmentCollection left, HeaderSegmentCollection right) { return !left.Equals(right); } #endregion public Enumerator GetEnumerator() { return new Enumerator(_headers); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } internal struct Enumerator : IEnumerator { private readonly string[] _headers; private int _index; private string _header; private int _headerLength; private int _offset; private int _leadingStart; private int _leadingEnd; private int _valueStart; private int _valueEnd; private int _trailingStart; private Mode _mode; private static readonly string[] NoHeaders = new string[0]; public Enumerator(string[] headers) { _headers = headers ?? NoHeaders; _header = string.Empty; _headerLength = -1; _index = -1; _offset = -1; _leadingStart = -1; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; _mode = Mode.Leading; } private enum Mode { Leading, Value, ValueQuoted, Trailing, Produce, } private enum Attr { Value, Quote, Delimiter, Whitespace } public HeaderSegment Current { get { return new HeaderSegment( new StringSegment(_header, _leadingStart, _leadingEnd - _leadingStart), new StringSegment(_header, _valueStart, _valueEnd - _valueStart)); } } object IEnumerator.Current { get { return Current; } } public void Dispose() { } public bool MoveNext() { while (true) { if (_mode == Mode.Produce) { _leadingStart = _trailingStart; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; if (_offset == _headerLength && _leadingStart != -1 && _leadingStart != _offset) { // Also produce trailing whitespace _leadingEnd = _offset; return true; } _mode = Mode.Leading; } // if end of a string if (_offset == _headerLength) { ++_index; _offset = -1; _leadingStart = 0; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; // if that was the last string if (_index == _headers.Length) { // no more move nexts return false; } // grab the next string _header = _headers[_index] ?? string.Empty; _headerLength = _header.Length; } while (true) { ++_offset; char ch = _offset == _headerLength ? (char)0 : _header[_offset]; // todo - array of attrs Attr attr = char.IsWhiteSpace(ch) ? Attr.Whitespace : ch == '\"' ? Attr.Quote : (ch == ',' || ch == (char)0) ? Attr.Delimiter : Attr.Value; switch (_mode) { case Mode.Leading: switch (attr) { case Attr.Delimiter: _leadingEnd = _offset; _mode = Mode.Produce; break; case Attr.Quote: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.ValueQuoted; break; case Attr.Value: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; case Mode.Value: switch (attr) { case Attr.Quote: _mode = Mode.ValueQuoted; break; case Attr.Delimiter: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; break; case Attr.Value: // more break; case Attr.Whitespace: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Trailing; break; } break; case Mode.ValueQuoted: switch (attr) { case Attr.Quote: _mode = Mode.Value; break; case Attr.Delimiter: if (ch == (char)0) { _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; } break; case Attr.Value: case Attr.Whitespace: // more break; } break; case Mode.Trailing: switch (attr) { case Attr.Delimiter: _mode = Mode.Produce; break; case Attr.Quote: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.ValueQuoted; break; case Attr.Value: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; } if (_mode == Mode.Produce) { return true; } } } } public void Reset() { _index = 0; _offset = 0; _leadingStart = 0; _leadingEnd = 0; _valueStart = 0; _valueEnd = 0; } } } internal struct StringSegment : IEquatable { private readonly string _buffer; private readonly int _offset; private readonly int _count; // // Initializes a new instance of the class. // public StringSegment(string buffer, int offset, int count) { _buffer = buffer; _offset = offset; _count = count; } public string Buffer { get { return _buffer; } } public int Offset { get { return _offset; } } public int Count { get { return _count; } } public string Value { get { return _offset == -1 ? null : _buffer.Substring(_offset, _count); } } public bool HasValue { get { return _offset != -1 && _count != 0 && _buffer != null; } } #region Equality members public bool Equals(StringSegment other) { return string.Equals(_buffer, other._buffer) && _offset == other._offset && _count == other._count; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is StringSegment && Equals((StringSegment)obj); } public override int GetHashCode() { unchecked { int hashCode = (_buffer != null ? _buffer.GetHashCode() : 0); hashCode = (hashCode * 397) ^ _offset; hashCode = (hashCode * 397) ^ _count; return hashCode; } } public static bool operator ==(StringSegment left, StringSegment right) { return left.Equals(right); } public static bool operator !=(StringSegment left, StringSegment right) { return !left.Equals(right); } #endregion public bool StartsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public bool EndsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset + _count - textLength, text, 0, textLength, comparisonType) == 0; } public bool Equals(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count != textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public string Substring(int offset, int length) { return _buffer.Substring(_offset + offset, length); } public StringSegment Subsegment(int offset, int length) { return new StringSegment(_buffer, _offset + offset, length); } public override string ToString() { return Value ?? string.Empty; } } internal static partial class OwinHelpers { private static readonly Action AddCookieCallback = (name, value, state) => { var dictionary = (IDictionary)state; if (!dictionary.ContainsKey(name)) { dictionary.Add(name, value); } }; private static readonly char[] SemicolonAndComma = new[] { ';', ',' }; internal static IDictionary GetCookies(IOwinRequest request) { var cookies = request.Get>("Microsoft.Owin.Cookies#dictionary"); if (cookies == null) { cookies = new Dictionary(StringComparer.Ordinal); request.Set("Microsoft.Owin.Cookies#dictionary", cookies); } string text = GetHeader(request.Headers, "Cookie"); if (request.Get("Microsoft.Owin.Cookies#text") != text) { cookies.Clear(); ParseDelimited(text, SemicolonAndComma, AddCookieCallback, cookies); request.Set("Microsoft.Owin.Cookies#text", text); } return cookies; } internal static void ParseDelimited(string text, char[] delimiters, Action callback, object state) { int textLength = text.Length; int equalIndex = text.IndexOf('='); if (equalIndex == -1) { equalIndex = textLength; } int scanIndex = 0; while (scanIndex < textLength) { int delimiterIndex = text.IndexOfAny(delimiters, scanIndex); if (delimiterIndex == -1) { delimiterIndex = textLength; } if (equalIndex < delimiterIndex) { while (scanIndex != equalIndex && char.IsWhiteSpace(text[scanIndex])) { ++scanIndex; } string name = text.Substring(scanIndex, equalIndex - scanIndex); string value = text.Substring(equalIndex + 1, delimiterIndex - equalIndex - 1); callback( Uri.UnescapeDataString(name.Replace('+', ' ')), Uri.UnescapeDataString(value.Replace('+', ' ')), state); equalIndex = text.IndexOf('=', delimiterIndex); if (equalIndex == -1) { equalIndex = textLength; } } scanIndex = delimiterIndex + 1; } } } internal static partial class OwinHelpers { public static string GetHeader(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : string.Join(",", values); } public static IEnumerable GetHeaderSplit(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : GetHeaderSplitImplementation(values); } private static IEnumerable GetHeaderSplitImplementation(string[] values) { foreach (var segment in new HeaderSegmentCollection(values)) { if (segment.Data.HasValue) { yield return DeQuote(segment.Data.Value); } } } public static string[] GetHeaderUnmodified(IDictionary headers, string key) { if (headers == null) { throw new ArgumentNullException("headers"); } string[] values; return headers.TryGetValue(key, out values) ? values : null; } public static void SetHeader(IDictionary headers, string key, string value) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (string.IsNullOrWhiteSpace(value)) { headers.Remove(key); } else { headers[key] = new[] { value }; } } public static void SetHeaderJoined(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = new[] { string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } // Quote items that contain comas and are not already quoted. private static string QuoteIfNeeded(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Contains(',')) { if (value[0] != '"' || value[value.Length - 1] != '"') { value = '"' + value + '"'; } } return value; } private static string DeQuote(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"') { value = value.Substring(1, value.Length - 2); } return value; } public static void SetHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = values; } } public static void SetHeaderUnmodified(IDictionary headers, string key, IEnumerable values) { if (headers == null) { throw new ArgumentNullException("headers"); } headers[key] = values.ToArray(); } public static void AppendHeader(IDictionary headers, string key, string values) { if (string.IsNullOrWhiteSpace(values)) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeader(headers, key, values); } else { headers[key] = new[] { existing + "," + values }; } } public static void AppendHeaderJoined(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeaderJoined(headers, key, values); } else { headers[key] = new[] { existing + "," + string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } public static void AppendHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string[] existing = GetHeaderUnmodified(headers, key); if (existing == null) { SetHeaderUnmodified(headers, key, values); } else { SetHeaderUnmodified(headers, key, existing.Concat(values)); } } } internal static partial class OwinHelpers { private static readonly Action AppendItemCallback = (name, value, state) => { var dictionary = (IDictionary>)state; List existing; if (!dictionary.TryGetValue(name, out existing)) { dictionary.Add(name, new List(1) { value }); } else { existing.Add(value); } }; private static readonly char[] AmpersandAndSemicolon = new[] { '&', ';' }; internal static IDictionary GetQuery(IOwinRequest request) { var query = request.Get>("Microsoft.Owin.Query#dictionary"); if (query == null) { query = new Dictionary(StringComparer.OrdinalIgnoreCase); request.Set("Microsoft.Owin.Query#dictionary", query); } string text = request.QueryString.Value; if (request.Get("Microsoft.Owin.Query#text") != text) { query.Clear(); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, AmpersandAndSemicolon, AppendItemCallback, accumulator); foreach (var kv in accumulator) { query.Add(kv.Key, kv.Value.ToArray()); } request.Set("Microsoft.Owin.Query#text", text); } return query; } internal static IFormCollection GetForm(string text) { IDictionary form = new Dictionary(StringComparer.OrdinalIgnoreCase); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, new[] { '&' }, AppendItemCallback, accumulator); foreach (var kv in accumulator) { form.Add(kv.Key, kv.Value.ToArray()); } return new FormCollection(form); } internal static string GetJoinedValue(IDictionary store, string key) { string[] values = GetUnmodifiedValues(store, key); return values == null ? null : string.Join(",", values); } internal static string[] GetUnmodifiedValues(IDictionary store, string key) { if (store == null) { throw new ArgumentNullException("store"); } string[] values; return store.TryGetValue(key, out values) ? values : null; } } internal static partial class OwinHelpers { internal static string GetHost(IOwinRequest request) { IHeaderDictionary headers = request.Headers; string host = GetHeader(headers, "Host"); if (!string.IsNullOrWhiteSpace(host)) { return host; } string localIpAddress = request.LocalIpAddress ?? "localhost"; var localPort = request.Get(OwinConstants.CommonKeys.LocalPort); return string.IsNullOrWhiteSpace(localPort) ? localIpAddress : (localIpAddress + ":" + localPort); } } } namespace LibOwin { using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Text; using System.Threading; using System.Threading.Tasks; using LibOwin.Infrastructure; #if LIBOWIN_PUBLIC public partial class CookieOptions { } public partial class FormCollection { } public partial class HeaderDictionary { } public partial struct HostString { } public partial interface IFormCollection { } public partial interface IHeaderDictionary { } public partial interface IOwinContext { } public partial interface IOwinRequest { } public partial interface IOwinResponse { } public partial interface IReadableStringCollection { } public partial class OwinContext { } public partial class OwinRequest { } public partial class OwinResponse { } public partial struct PathString { } public partial struct QueryString { } public partial class ReadableStringCollection { } public partial class RequestCookieCollection { } public partial class ResponseCookieCollection { } public partial class IOwinResponseExtension { } #endif /// /// Options used to create a new cookie. /// partial class CookieOptions { /// /// Creates a default cookie with a path of '/'. /// public CookieOptions() { Path = "/"; } /// /// Gets or sets the domain to associate the cookie with. /// /// The domain to associate the cookie with. public string Domain { get; set; } /// /// Gets or sets the cookie path. /// /// The cookie path. public string Path { get; set; } /// /// Gets or sets the expiration date and time for the cookie. /// /// The expiration date and time for the cookie. public DateTime? Expires { get; set; } /// /// Gets or sets a value that indicates whether to transmit the cookie using Secure Sockets Layer (SSL)—that is, over HTTPS only. /// /// true to transmit the cookie only over an SSL connection (HTTPS); otherwise, false. public bool Secure { get; set; } /// /// Gets or sets a value that indicates whether a cookie is accessible by client-side script. /// /// true if a cookie is accessible by client-side script; otherwise, false. public bool HttpOnly { get; set; } } /// /// Contains the parsed form values. /// partial class FormCollection : ReadableStringCollection, IFormCollection { /// /// Initializes a new instance of the class. /// /// The store for the form. public FormCollection(IDictionary store) : base(store) {} } /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial class HeaderDictionary : IHeaderDictionary { /// /// Initializes a new instance of the class. /// /// The underlying data store. public HeaderDictionary(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Gets an that contains the keys in the ;. /// /// An that contains the keys in the . public ICollection Keys { get { return Store.Keys; } } /// /// /// public ICollection Values { get { return Store.Values; } } /// /// Gets the number of elements contained in the ;. /// /// The number of elements contained in the . public int Count { get { return Store.Count; } } /// /// Gets a value that indicates whether the is in read-only mode. /// /// true if the is in read-only mode; otherwise, false. public bool IsReadOnly { get { return Store.IsReadOnly; } } /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string this[string key] { get { return Get(key); } set { Set(key, value); } } /// /// Throws KeyNotFoundException if the key is not present. /// /// The header name. /// string[] IDictionary.this[string key] { get { return Store[key]; } set { Store[key] = value; } } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Get the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string Get(string key) { return OwinHelpers.GetHeader(Store, key); } /// /// Get the associated values from the collection without modification. /// /// The header name. /// the associated value from the collection without modification, or null if the key is not present. public IList GetValues(string key) { return OwinHelpers.GetHeaderUnmodified(Store, key); } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. public IList GetCommaSeparatedValues(string key) { IEnumerable values = OwinHelpers.GetHeaderSplit(Store, key); return values == null ? null : values.ToList(); } /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. public void Append(string key, string value) { OwinHelpers.AppendHeader(Store, key, value); } /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. public void AppendValues(string key, params string[] values) { OwinHelpers.AppendHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. public void AppendCommaSeparatedValues(string key, params string[] values) { OwinHelpers.AppendHeaderJoined(Store, key, values); } /// /// Sets a specific header value. /// /// The header name. /// The header value. public void Set(string key, string value) { OwinHelpers.SetHeader(Store, key, value); } /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. public void SetValues(string key, params string[] values) { OwinHelpers.SetHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. public void SetCommaSeparatedValues(string key, params string[] values) { OwinHelpers.SetHeaderJoined(Store, key, values); } /// /// Adds the given header and values to the collection. /// /// The header name. /// The header values. public void Add(string key, string[] value) { Store.Add(key, value); } /// /// Determines whether the contains a specific key. /// /// The key. /// true if the contains a specific key; otherwise, false. public bool ContainsKey(string key) { return Store.ContainsKey(key); } /// /// Removes the given header from the collection. /// /// The header name. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(string key) { return Store.Remove(key); } /// /// Retrieves a value from the dictionary. /// /// The header name. /// The value. /// true if the contains the key; otherwise, false. public bool TryGetValue(string key, out string[] value) { return Store.TryGetValue(key, out value); } /// /// Adds a new list of items to the collection. /// /// The item to add. public void Add(KeyValuePair item) { Store.Add(item); } /// /// Clears the entire list of objects. /// public void Clear() { Store.Clear(); } /// /// Returns a value indicating whether the specified object occurs within this collection. /// /// The item. /// true if the specified object occurs within this collection; otherwise, false. public bool Contains(KeyValuePair item) { return Store.Contains(item); } /// /// Copies the elements to a one-dimensional Array instance at the specified index. /// /// The one-dimensional Array that is the destination of the specified objects copied from the . /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) { Store.CopyTo(array, arrayIndex); } /// /// Removes the given item from the the collection. /// /// The item. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(KeyValuePair item) { return Store.Remove(item); } } /// /// Represents the host portion of a Uri can be used to construct Uri's properly formatted and encoded for use in /// HTTP headers. /// partial struct HostString : IEquatable { private readonly string _value; /// /// Creates a new HostString without modification. The value should be Unicode rather than punycode, and may have a port. /// IPv4 and IPv6 addresses are also allowed, and also may have ports. /// /// public HostString(string value) { _value = value; } /// /// Returns the original value from the constructor. /// public string Value { get { return _value; } } /// /// Returns the value as normalized by ToUriComponent(). /// /// public override string ToString() { return ToUriComponent(); } /// /// Returns the value properly formatted and encoded for use in a URI in a HTTP header. /// Any Unicode is converted to punycode. IPv6 addresses will have brackets added if they are missing. /// /// public string ToUriComponent() { int index; if (string.IsNullOrEmpty(_value)) { return string.Empty; } else if (_value.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port return _value; } else if ((index = _value.IndexOf(':')) >= 0 && index < _value.Length - 1 && _value.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons return "[" + _value + "]"; } else if (index >= 0) { // Has a port string port = _value.Substring(index); var mapping = new IdnMapping(); return mapping.GetAscii(_value, 0, index) + port; } else { var mapping = new IdnMapping(); return mapping.GetAscii(_value); } } /// /// Creates a new HostString from the given uri component. /// Any punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(string uriComponent) { if (!string.IsNullOrEmpty(uriComponent)) { int index; if (uriComponent.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port } else if ((index = uriComponent.IndexOf(':')) >= 0 && index < uriComponent.Length - 1 && uriComponent.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons } else if (uriComponent.IndexOf("xn--", StringComparison.Ordinal) >= 0) { // Contains punycode if (index >= 0) { // Has a port string port = uriComponent.Substring(index); IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent, 0, index) + port; } else { IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent); } } } return new HostString(uriComponent); } /// /// Creates a new HostString from the host and port of the give Uri instance. /// Punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new HostString(uri.GetComponents( UriComponents.NormalizedHost | // Always convert punycode to Unicode. UriComponents.HostAndPort, UriFormat.Unescaped)); } /// /// Compares the equality of the Value property, ignoring case. /// /// /// public bool Equals(HostString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares against the given object only if it is a HostString. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HostString && Equals((HostString)obj); } /// /// Gets a hash code for the value. /// /// public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(HostString left, HostString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(HostString left, HostString right) { return !left.Equals(right); } } /// /// Contains the parsed form values. /// partial interface IFormCollection : IReadableStringCollection {} /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial interface IHeaderDictionary : IReadableStringCollection, IDictionary { /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. new string this[string key] { get; set; } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. IList GetCommaSeparatedValues(string key); /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. void Append(string key, string value); /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. void AppendValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. void AppendCommaSeparatedValues(string key, params string[] values); /// /// Sets a specific header value. /// /// The header name. /// The header value. void Set(string key, string value); /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. void SetValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. void SetCommaSeparatedValues(string key, params string[] values); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinContext { /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. IOwinRequest Request { get; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. IOwinResponse Response { get; } /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. TextWriter TraceOutput { get; set; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinContext Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinRequest { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or set the HTTP method. /// /// The HTTP method. string Method { get; set; } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. string Scheme { get; set; } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. bool IsSecure { get; } /// /// Gets or set the Host header. May include the port. /// /// The Host header. HostString Host { get; set; } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. PathString PathBase { get; set; } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. PathString Path { get; set; } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. QueryString QueryString { get; set; } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. IReadableStringCollection Query { get; } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. Uri Uri { get; } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. string Protocol { get; set; } /// /// Gets the request headers. /// /// The request headers. IHeaderDictionary Headers { get; } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. RequestCookieCollection Cookies { get; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. string CacheControl { get; set; } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. string MediaType { get; set; } /// /// Gets or set the Accept header. /// /// The Accept header. string Accept { get; set; } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. Stream Body { get; set; } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. CancellationToken CallCancelled { get; set; } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. string LocalIpAddress { get; set; } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. int? LocalPort { get; set; } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. string RemoteIpAddress { get; set; } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. int? RemotePort { get; set; } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. ClaimsPrincipal User { get; set; } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. Task ReadFormAsync(); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinRequest Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinResponse { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. int StatusCode { get; set; } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. string ReasonPhrase { get; set; } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. string Protocol { get; set; } /// /// Gets the response header collection. /// /// The response header collection. IHeaderDictionary Headers { get; } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. ResponseCookieCollection Cookies { get; } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. long? ContentLength { get; set; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Expires header. /// /// The Expires header. DateTimeOffset? Expires { get; set; } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. string ETag { get; set; } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. Stream Body { get; set; } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. void OnSendingHeaders(Action callback, object state); /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. void Redirect(string location); /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. void Write(string text); /// /// Writes the given bytes to the response body stream. /// /// The response data. void Write(byte[] data); /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. void Write(byte[] data, int offset, int count); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(string text); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(string text, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, int offset, int count, CancellationToken token); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinResponse Set(string key, T value); } /// /// Accessors for headers, query, forms, etc. /// partial interface IReadableStringCollection : IEnumerable> { /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string this[string key] { get; } // Joined /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string Get(string key); // Joined /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// IList GetValues(string key); // Raw } internal static class OwinConstants { #region OWIN v1.0.0 - 3.2.1. Request Data // http://owin.org/spec/owin-1.0.0.html public const string RequestScheme = "owin.RequestScheme"; public const string RequestMethod = "owin.RequestMethod"; public const string RequestPathBase = "owin.RequestPathBase"; public const string RequestPath = "owin.RequestPath"; public const string RequestQueryString = "owin.RequestQueryString"; public const string RequestProtocol = "owin.RequestProtocol"; public const string RequestHeaders = "owin.RequestHeaders"; public const string RequestBody = "owin.RequestBody"; public const string RequestUser = "owin.RequestUser"; //owin 1.0.1 #endregion #region OWIN v1.0.0 - 3.2.2. Response Data // http://owin.org/spec/owin-1.0.0.html public const string ResponseStatusCode = "owin.ResponseStatusCode"; public const string ResponseReasonPhrase = "owin.ResponseReasonPhrase"; public const string ResponseProtocol = "owin.ResponseProtocol"; public const string ResponseHeaders = "owin.ResponseHeaders"; public const string ResponseBody = "owin.ResponseBody"; #endregion #region OWIN v1.0.0 - 3.2.3. Other Data // http://owin.org/spec/owin-1.0.0.html public const string CallCancelled = "owin.CallCancelled"; public const string OwinVersion = "owin.Version"; #endregion #region OWIN Keys for IAppBuilder.Properties internal static class Builder { public const string AddSignatureConversion = "builder.AddSignatureConversion"; public const string DefaultApp = "builder.DefaultApp"; } #endregion #region OWIN Key Guidelines and Common Keys - 6. Common keys // http://owin.org/spec/CommonKeys.html internal static class CommonKeys { public const string ClientCertificate = "ssl.ClientCertificate"; public const string RemoteIpAddress = "server.RemoteIpAddress"; public const string RemotePort = "server.RemotePort"; public const string LocalIpAddress = "server.LocalIpAddress"; public const string LocalPort = "server.LocalPort"; public const string IsLocal = "server.IsLocal"; public const string TraceOutput = "host.TraceOutput"; public const string Addresses = "host.Addresses"; public const string AppName = "host.AppName"; public const string Capabilities = "server.Capabilities"; public const string OnSendingHeaders = "server.OnSendingHeaders"; public const string OnAppDisposing = "host.OnAppDisposing"; public const string Scheme = "scheme"; public const string Host = "host"; public const string Port = "port"; public const string Path = "path"; } #endregion #region SendFiles v0.3.0 // http://owin.org/extensions/owin-SendFile-Extension-v0.3.0.htm internal static class SendFiles { // 3.1. Startup public const string Version = "sendfile.Version"; public const string Support = "sendfile.Support"; public const string Concurrency = "sendfile.Concurrency"; // 3.2. Per Request public const string SendAsync = "sendfile.SendAsync"; } #endregion #region Opaque v0.3.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class OpaqueConstants { // 3.1. Startup public const string Version = "opaque.Version"; // 3.2. Per Request public const string Upgrade = "opaque.Upgrade"; // 5. Consumption public const string Stream = "opaque.Stream"; // public const string Version = "opaque.Version"; // redundant, declared above public const string CallCancelled = "opaque.CallCancelled"; } #endregion #region WebSocket v0.4.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class WebSocket { // 3.1. Startup public const string Version = "websocket.Version"; // 3.2. Per Request public const string Accept = "websocket.Accept"; // 4. Accept public const string SubProtocol = "websocket.SubProtocol"; // 5. Consumption public const string SendAsync = "websocket.SendAsync"; public const string ReceiveAsync = "websocket.ReceiveAsync"; public const string CloseAsync = "websocket.CloseAsync"; // public const string Version = "websocket.Version"; // redundant, declared above public const string CallCancelled = "websocket.CallCancelled"; public const string ClientCloseStatus = "websocket.ClientCloseStatus"; public const string ClientCloseDescription = "websocket.ClientCloseDescription"; } #endregion #region Security v0.1.0 // http://owin.org/extensions/owin-Security-Extension-v0.1.0.htm internal static class Security { // 3.2. Per Request public const string User = "server.User"; public const string Authenticate = "security.Authenticate"; // 3.3. Response public const string SignIn = "security.SignIn"; public const string SignOut = "security.SignOut"; public const string SignOutProperties = "security.SignOutProperties"; public const string Challenge = "security.Challenge"; } #endregion } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinContext : IOwinContext { /// /// Create a new context with only request and response header collections. /// public OwinContext() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Create a new wrapper. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinContext(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. public virtual IOwinRequest Request { get; private set; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. public virtual IOwinResponse Response { get; private set; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. public virtual TextWriter TraceOutput { get { return Get(OwinConstants.CommonKeys.TraceOutput); } set { Set(OwinConstants.CommonKeys.TraceOutput, value); } } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinContext Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinRequest : IOwinRequest { /// /// Create a new context with only request and response header collections. /// public OwinRequest() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Create a new environment wrapper exposing request properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinRequest(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or set the HTTP method. /// /// The HTTP method. public virtual string Method { get { return Get(OwinConstants.RequestMethod); } set { Set(OwinConstants.RequestMethod, value); } } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. public virtual string Scheme { get { return Get(OwinConstants.RequestScheme); } set { Set(OwinConstants.RequestScheme, value); } } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. public virtual bool IsSecure { get { return string.Equals(Scheme, Constants.Https, StringComparison.OrdinalIgnoreCase); } } /// /// Gets or set the Host header. May include the port. /// /// The Host header. public virtual HostString Host { get { return new HostString(OwinHelpers.GetHost(this)); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Host, value.Value); } } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. public virtual PathString PathBase { get { return new PathString(Get(OwinConstants.RequestPathBase)); } set { Set(OwinConstants.RequestPathBase, value.Value); } } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. public virtual PathString Path { get { return new PathString(Get(OwinConstants.RequestPath)); } set { Set(OwinConstants.RequestPath, value.Value); } } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. public virtual QueryString QueryString { get { return new QueryString(Get(OwinConstants.RequestQueryString)); } set { Set(OwinConstants.RequestQueryString, value.Value); } } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. public virtual IReadableStringCollection Query { get { return new ReadableStringCollection(OwinHelpers.GetQuery(this)); } } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. public virtual Uri Uri { get { return new Uri(Scheme + "://" + Host + PathBase + Path + QueryString); } } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. public virtual string Protocol { get { return Get(OwinConstants.RequestProtocol); } set { Set(OwinConstants.RequestProtocol, value); } } /// /// Gets the request headers. /// /// The request headers. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.RequestHeaders); } } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. public RequestCookieCollection Cookies { get { return new RequestCookieCollection(OwinHelpers.GetCookies(this)); } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. public virtual string CacheControl { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.CacheControl); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.CacheControl, value); } } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. public virtual string MediaType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.MediaType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.MediaType, value); } } /// /// Gets or set the Accept header. /// /// The Accept header. public virtual string Accept { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Accept); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Accept, value); } } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. public virtual Stream Body { get { return Get(OwinConstants.RequestBody); } set { Set(OwinConstants.RequestBody, value); } } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. public virtual CancellationToken CallCancelled { get { return Get(OwinConstants.CallCancelled); } set { Set(OwinConstants.CallCancelled, value); } } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. public virtual string LocalIpAddress { get { return Get(OwinConstants.CommonKeys.LocalIpAddress); } set { Set(OwinConstants.CommonKeys.LocalIpAddress, value); } } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. public virtual int? LocalPort { get { int value; if (int.TryParse(LocalPortString, out value)) { return value; } return null; } set { if (value.HasValue) { LocalPortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.LocalPort); } } } private string LocalPortString { get { return Get(OwinConstants.CommonKeys.LocalPort); } set { Set(OwinConstants.CommonKeys.LocalPort, value); } } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. public virtual string RemoteIpAddress { get { return Get(OwinConstants.CommonKeys.RemoteIpAddress); } set { Set(OwinConstants.CommonKeys.RemoteIpAddress, value); } } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. public virtual int? RemotePort { get { int value; if (int.TryParse(RemotePortString, out value)) { return value; } return null; } set { if (value.HasValue) { RemotePortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.RemotePort); } } } private string RemotePortString { get { return Get(OwinConstants.CommonKeys.RemotePort); } set { Set(OwinConstants.CommonKeys.RemotePort, value); } } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. public virtual ClaimsPrincipal User { get { var claimsPrincipal = Get(OwinConstants.RequestUser); return claimsPrincipal ?? Get(OwinConstants.Security.User) as ClaimsPrincipal; } set { Set(OwinConstants.RequestUser, value); } } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. public async Task ReadFormAsync() { var form = Get("Microsoft.Owin.Form#collection"); if (form == null) { string text; // Don't close, it prevents re-winding. using (var reader = new StreamReader(Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 4 * 1024, leaveOpen: true)) { text = await reader.ReadToEndAsync(); } form = OwinHelpers.GetForm(text); Set("Microsoft.Owin.Form#collection", form); } return form; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinRequest Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinResponse : IOwinResponse { /// /// Create a new context with only request and response header collections. /// public OwinResponse() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Creates a new environment wrapper exposing response properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinResponse(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. public virtual int StatusCode { get { return Get(OwinConstants.ResponseStatusCode, 200); } set { Set(OwinConstants.ResponseStatusCode, value); } } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. public virtual string ReasonPhrase { get { return Get(OwinConstants.ResponseReasonPhrase); } set { Set(OwinConstants.ResponseReasonPhrase, value); } } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. public virtual string Protocol { get { return Get(OwinConstants.ResponseProtocol); } set { Set(OwinConstants.ResponseProtocol, value); } } /// /// Gets the response header collection. /// /// The response header collection. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.ResponseHeaders); } } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. public virtual ResponseCookieCollection Cookies { get { return new ResponseCookieCollection(Headers); } } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. public virtual long? ContentLength { get { long value; if (long.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentLength), out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentLength, value.Value.ToString(CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.ContentLength); } } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Expires header. /// /// The Expires header. public virtual DateTimeOffset? Expires { get { DateTimeOffset value; if (DateTimeOffset.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Expires), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Expires, value.Value.ToString(Constants.HttpDateFormat, CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.Expires); } } } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. public virtual string ETag { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ETag); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ETag, value); } } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. public virtual Stream Body { get { return Get(OwinConstants.ResponseBody); } set { Set(OwinConstants.ResponseBody, value); } } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. public virtual void OnSendingHeaders(Action callback, object state) { var onSendingHeaders = Get, object>>(OwinConstants.CommonKeys.OnSendingHeaders); if (onSendingHeaders == null) { throw new NotSupportedException(Resources.Exception_MissingOnSendingHeaders); } onSendingHeaders(callback, state); } /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. public virtual void Redirect(string location) { StatusCode = 302; OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Location, location); } /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. public virtual void Write(string text) { Write(Encoding.UTF8.GetBytes(text)); } /// /// Writes the given bytes to the response body stream. /// /// The response data. public virtual void Write(byte[] data) { Write(data, 0, data == null ? 0 : data.Length); } /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. public virtual void Write(byte[] data, int offset, int count) { Body.Write(data, offset, count); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text) { return WriteAsync(text, CancellationToken.None); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text, CancellationToken token) { return WriteAsync(Encoding.UTF8.GetBytes(text), token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data) { return WriteAsync(data, CancellationToken.None); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, CancellationToken token) { return WriteAsync(data, 0, data == null ? 0 : data.Length, token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, int offset, int count, CancellationToken token) { return Body.WriteAsync(data, offset, count, token); } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { return Get(key, default(T)); } private T Get(string key, T fallback) { object value; return Environment.TryGetValue(key, out value) ? (T)value : fallback; } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinResponse Set(string key, T value) { Environment[key] = value; return this; } } /// /// Provides correct escaping for Path and PathBase values when needed to reconstruct a request or redirect URI string /// partial struct PathString : IEquatable { private static readonly Func EscapeDataString = Uri.EscapeDataString; /// /// Represents the empty path. This field is read-only. /// public static readonly PathString Empty = new PathString(String.Empty); private readonly string _value; /// /// Initialize the path string with a given value. This value must be in un-escaped format. Use /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format. /// /// The unescaped path to be assigned to the Value property. public PathString(string value) { if (!String.IsNullOrEmpty(value) && value[0] != '/') { throw new ArgumentException(Resources.Exception_PathMustStartWithSlash, "value"); } _value = value; } /// /// The unescaped path value /// public string Value { get { return _value; } } /// /// True if the path is not empty /// public bool HasValue { get { return !String.IsNullOrEmpty(_value); } } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public override string ToString() { return ToUriComponent(); } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public string ToUriComponent() { if (HasValue) { if (RequiresEscaping(_value)) { // TODO: Measure the cost of this escaping and consider optimizing. return String.Join("/", _value.Split('/').Select(EscapeDataString)); } return _value; } return String.Empty; } // Very conservative, these characters do not need to be escaped in a path. private static bool RequiresEscaping(string value) { for (int i = 0; i < value.Length; i++) { char c = value[i]; // Check conservatively for safe characters. See http://www.ietf.org/rfc/rfc3986.txt bool safeChar = (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '/' || c == '-' || c == '_'); if (!safeChar) { return true; } } return false; } /// /// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a path. /// /// The escaped path as it appears in the URI format. /// The resulting PathString public static PathString FromUriComponent(string uriComponent) { // REVIEW: what is the exactly correct thing to do? return new PathString(Uri.UnescapeDataString(uriComponent)); } /// /// Returns an PathString given the path as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting PathString public static PathString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } // REVIEW: what is the exactly correct thing to do? return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)); } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// public bool StartsWithSegments(PathString other) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { return value1.Length == value2.Length || value1[value2.Length] == '/'; } return false; } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// Any remaining segments from this instance not included in the other instance. /// public bool StartsWithSegments(PathString other, out PathString remaining) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { if (value1.Length == value2.Length || value1[value2.Length] == '/') { remaining = new PathString(value1.Substring(value2.Length)); return true; } } remaining = Empty; return false; } /// /// Adds two PathString instances into a combined PathString value. /// /// The combined PathString value public PathString Add(PathString other) { return new PathString(Value + other.Value); } /// /// Combines a PathString and QueryString into the joined URI formatted string value. /// /// The joined URI formatted string value public string Add(QueryString other) { return ToUriComponent() + other.ToUriComponent(); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public bool Equals(PathString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares this PathString value to another value using a specific StringComparison type /// /// The second PathString for comparison /// The StringComparison type to use /// True if both PathString values are equal public bool Equals(PathString other, StringComparison comparisonType) { return string.Equals(_value, other._value, comparisonType); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is PathString && Equals((PathString)obj, StringComparison.OrdinalIgnoreCase); } /// /// Returns the hash code for the PathString value. The hash code is provided by the OrdinalIgnoreCase implementation. /// /// The hash code public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are equal public static bool operator ==(PathString left, PathString right) { return left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are not equal public static bool operator !=(PathString left, PathString right) { return !left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static PathString operator +(PathString left, PathString right) { return left.Add(right); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static string operator +(PathString left, QueryString right) { return left.Add(right); } } /// /// Provides correct handling for QueryString value when needed to reconstruct a request or redirect URI string /// partial struct QueryString : IEquatable { /// /// Represents the empty query string. This field is read-only. /// public static readonly QueryString Empty = new QueryString(String.Empty); private readonly string _value; /// /// Initalize the query string with a given value. This value must be in escaped and delimited format without /// a leading '?' character. /// /// The query string to be assigned to the Value property. public QueryString(string value) { _value = value; } /// /// Initialize a query string with a single given parameter name and value. The value is /// /// The unencoded parameter name /// The unencoded parameter value public QueryString(string name, string value) { _value = Uri.EscapeDataString(name) + '=' + Uri.EscapeDataString(value); } /// /// The unescaped query string without the leading '?' character /// public string Value { get { return _value; } } /// /// True if the query string is not empty /// public bool HasValue { get { return !String.IsNullOrWhiteSpace(_value); } } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentally /// dangerous are escaped. /// /// The query string value public override string ToString() { return ToUriComponent(); } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentially /// dangerous are escaped. /// /// The query string value public string ToUriComponent() { // Escape things properly so System.Uri doesn't mis-interpret the data. return HasValue ? "?" + _value.Replace("#", "%23") : String.Empty; } /// /// Returns an QueryString given the query as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a query. /// /// The escaped query as it appears in the URI format. /// The resulting QueryString public static QueryString FromUriComponent(string uriComponent) { if (String.IsNullOrEmpty(uriComponent)) { return new QueryString(string.Empty); } if (uriComponent[0] != '?') { throw new ArgumentException(Resources.Exception_QueryStringMustStartWithDelimiter, "uriComponent"); } return new QueryString(uriComponent.Substring(1)); } /// /// Returns an QueryString given the query as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting QueryString public static QueryString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new QueryString(uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped)); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public bool Equals(QueryString other) { return string.Equals(_value, other._value); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is QueryString && Equals((QueryString)obj); } /// /// Returns the hash code for this instance. /// /// public override int GetHashCode() { return (_value != null ? _value.GetHashCode() : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(QueryString left, QueryString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(QueryString left, QueryString right) { return !left.Equals(right); } } /// /// Accessors for query, forms, etc. /// partial class ReadableStringCollection : IReadableStringCollection { /// /// Create a new wrapper /// /// public ReadableStringCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string this[string key] { get { return Get(key); } } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string Get(string key) { return OwinHelpers.GetJoinedValue(Store, key); } /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// public IList GetValues(string key) { string[] values; Store.TryGetValue(key, out values); return values; } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } /// /// A wrapper for the request Cookie header /// partial class RequestCookieCollection : IEnumerable> { /// /// Create a new wrapper /// /// public RequestCookieCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Returns null rather than throwing KeyNotFoundException /// /// /// public string this[string key] { get { string value; Store.TryGetValue(key, out value); return value; } } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } internal static class Resources { internal const string Exception_MissingOnSendingHeaders = "The OWIN key 'server.OnSsoendingHeaders' is not available for this request."; internal const string Exception_PathMustStartWithSlash = "The path must start with a '/' followed by one or more characters."; internal const string Exception_QueryStringMustStartWithDelimiter = "The query string must start with a '?' unless null or empty."; } /// /// A wrapper for the response Set-Cookie header /// partial class ResponseCookieCollection { /// /// Create a new wrapper /// /// public ResponseCookieCollection(IHeaderDictionary headers) { if (headers == null) { throw new ArgumentNullException("headers"); } Headers = headers; } private IHeaderDictionary Headers { get; set; } /// /// Add a new cookie and value /// /// /// public void Append(string key, string value) { Headers.AppendValues(Constants.Headers.SetCookie, Uri.EscapeDataString(key) + "=" + Uri.EscapeDataString(value) + "; path=/"); } /// /// Add a new cookie /// /// /// /// public void Append(string key, string value, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); bool expiresHasValue = options.Expires.HasValue; string setCookieValue = string.Concat( Uri.EscapeDataString(key), "=", Uri.EscapeDataString(value ?? string.Empty), !domainHasValue ? null : "; domain=", !domainHasValue ? null : options.Domain, !pathHasValue ? null : "; path=", !pathHasValue ? null : options.Path, !expiresHasValue ? null : "; expires=", !expiresHasValue ? null : options.Expires.Value.ToString("ddd, dd-MMM-yyyy HH:mm:ss ", CultureInfo.InvariantCulture) + "GMT", !options.Secure ? null : "; secure", !options.HttpOnly ? null : "; HttpOnly"); Headers.AppendValues("Set-Cookie", setCookieValue); } /// /// Sets an expired cookie /// /// public void Delete(string key) { Func predicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); var deleteCookies = new[] { Uri.EscapeDataString(key) + "=; expires=Thu, 01-Jan-1970 00:00:00 GMT" }; IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues == null || existingValues.Count == 0) { Headers.SetValues(Constants.Headers.SetCookie, deleteCookies); } else { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !predicate(value)).Concat(deleteCookies).ToArray()); } } /// /// Sets an expired cookie /// /// /// public void Delete(string key, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); Func rejectPredicate; if (domainHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("domain=" + options.Domain, StringComparison.OrdinalIgnoreCase) != -1; } else if (pathHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("path=" + options.Path, StringComparison.OrdinalIgnoreCase) != -1; } else { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); } IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues != null) { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !rejectPredicate(value)).ToArray()); } Append(key, string.Empty, new CookieOptions { Path = options.Path, Domain = options.Domain, Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), }); } } static partial class IOwinResponseExtension { /// /// Registers for an event that fires when the response headers are sent. /// /// The owin response /// The callback method. /// The callback state. public static void OnSendingHeaders(this IOwinResponse response, Action callback, T state) { if (response == null) { throw new ArgumentNullException("response"); } Action innerCallback = innerState => callback((T)innerState); response.OnSendingHeaders(innerCallback, state); } } } ================================================ FILE: src/Chapter11/MicroserviceNET.Platform/HttpClientFactory.cs ================================================ namespace MicroserviceNET.Platform { using System; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using IdentityModel.Client; public interface IHttpClientFactory { Task Create(Uri uri, string requestScope); } public class HttpClientFactory : IHttpClientFactory { private readonly TokenClient tokenClient; private readonly string correlationToken; private readonly string idToken; public HttpClientFactory(string tokenUrl, string clientName, string clientSecret, string correlationToken, string idToken) { this.tokenClient = new TokenClient(tokenUrl, clientName, clientSecret); this.correlationToken = correlationToken; this.idToken = idToken; } public async Task Create(Uri uri, string requestScope) { var response = await this.tokenClient.RequestClientCredentialsAsync(requestScope).ConfigureAwait(false); var client = new HttpClient() { BaseAddress = uri }; client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", response.AccessToken); client.DefaultRequestHeaders.Add("Correlation-Token", this.correlationToken); if (!string.IsNullOrEmpty(this.idToken)) client.DefaultRequestHeaders.Add("microservice.NET-end-user", this.idToken); return client; } } } ================================================ FILE: src/Chapter11/MicroserviceNET.Platform/MicroserviceNET.Platform.xproj ================================================  14.0.25123 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 927b3235-8756-4b92-99df-4f8d8adae645 MicroserviceNET.Platform .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter11/MicroserviceNET.Platform/MicroservicePlatformHelper.cs ================================================ namespace MicroserviceNET.Platform { using System.Security.Claims; using Nancy; using Nancy.Owin; using Nancy.TinyIoc; using LibOwin; public static class MicroservicePlatform { private static string TokenUrl; private static string ClientName; private static string ClientSecret; public static void Configure(string tokenUrl, string clientName, string clientSecret) { TokenUrl = tokenUrl; ClientName = clientName; ClientSecret = clientSecret; } public static TinyIoCContainer UseHttpClientFactory(this TinyIoCContainer self, NancyContext context) { var correlationToken = context.GetOwinEnvironment()?["correlationToken"] as string; object key = null; context.GetOwinEnvironment()?.TryGetValue(OwinConstants.RequestUser, out key); var principal = key as ClaimsPrincipal; var idToken = principal?.FindFirst("id_token"); self.Register(new HttpClientFactory(TokenUrl, ClientName, ClientSecret, correlationToken ?? "", idToken?.Value ?? "")); return self; } } } ================================================ FILE: src/Chapter11/MicroserviceNET.Platform/project.json ================================================ { "version": "1.0.0-*", "dependencies": { "NETStandard.Library": "1.5.0", "MicroserivceNET.Auth": { "target": "project" }, "MicroserviceNET.Logging": { "target": "project", "version": "1.0.0" }, "Nancy": "2.0.0-barneyrubble", "IdentityModel": "2.0.0-beta5" }, "frameworks": { "netstandard1.5": { "imports": "dnxcore50" } }, "tooling": { "defaultNamespace": "MicroserviceNET.Platform" } } ================================================ FILE: src/Chapter11/ch11.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MicroserviceNET.Logging", "MicroserviceNET.Logging\MicroserviceNET.Logging.xproj", "{F73DF2AB-708B-4916-AC28-E95117466511}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MicroserivceNET.Auth", "MicroserivceNET.Auth\MicroserivceNET.Auth.xproj", "{662415B4-3486-4B64-B1F9-718B9FF0879D}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MicroserviceNET.Platform", "MicroserviceNET.Platform\MicroserviceNET.Platform.xproj", "{927B3235-8756-4B92-99DF-4F8D8ADAE645}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "HelloMicroservicesPlatform", "HelloMicroservicesPlatform\HelloMicroservicesPlatform.xproj", "{DAEBD923-B213-4CA5-A240-B96686AEC974}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {F73DF2AB-708B-4916-AC28-E95117466511}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F73DF2AB-708B-4916-AC28-E95117466511}.Debug|Any CPU.Build.0 = Debug|Any CPU {F73DF2AB-708B-4916-AC28-E95117466511}.Release|Any CPU.ActiveCfg = Release|Any CPU {F73DF2AB-708B-4916-AC28-E95117466511}.Release|Any CPU.Build.0 = Release|Any CPU {662415B4-3486-4B64-B1F9-718B9FF0879D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {662415B4-3486-4B64-B1F9-718B9FF0879D}.Debug|Any CPU.Build.0 = Debug|Any CPU {662415B4-3486-4B64-B1F9-718B9FF0879D}.Release|Any CPU.ActiveCfg = Release|Any CPU {662415B4-3486-4B64-B1F9-718B9FF0879D}.Release|Any CPU.Build.0 = Release|Any CPU {927B3235-8756-4B92-99DF-4F8D8ADAE645}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {927B3235-8756-4B92-99DF-4F8D8ADAE645}.Debug|Any CPU.Build.0 = Debug|Any CPU {927B3235-8756-4B92-99DF-4F8D8ADAE645}.Release|Any CPU.ActiveCfg = Release|Any CPU {927B3235-8756-4B92-99DF-4F8D8ADAE645}.Release|Any CPU.Build.0 = Release|Any CPU {DAEBD923-B213-4CA5-A240-B96686AEC974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DAEBD923-B213-4CA5-A240-B96686AEC974}.Debug|Any CPU.Build.0 = Debug|Any CPU {DAEBD923-B213-4CA5-A240-B96686AEC974}.Release|Any CPU.ActiveCfg = Release|Any CPU {DAEBD923-B213-4CA5-A240-B96686AEC974}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: src/Chapter12/.idea.C12/riderModule.iml ================================================ ================================================ FILE: src/Chapter12/ApiGateway/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter12/ApiGateway/Dockerfile ================================================ FROM microsoft/dotnet:latest COPY . /app WORKDIR /app RUN ["dotnet", "restore"] RUN ["dotnet", "build"] EXPOSE 5000/tcp ENTRYPOINT ["dotnet", "run", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter12/ApiGateway/GatewayModule.cs ================================================ namespace ApiGateway { using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Net.Http; using MicroserviceNET.Platform; using Nancy; using Nancy.ModelBinding; using Newtonsoft.Json; using Serilog; using static System.Text.Encoding; public class GatewayModule : NancyModule { private static Product[] productList = new[] { new Product { ProductName = "Fancy shirt", ProductId = 1 }, new Product { ProductName = "Fancier shirt", ProductId = 2 } }; public GatewayModule(IHttpClientFactory clientFactory, ILogger logger) { Get("/productlist", async parameters => { var userId = (int)parameters.userid; var client = await clientFactory.Create(new Uri("http://localhost:5100/"), "product_catalog_read"); var response = await client.GetAsync("/products?productIds=1,2,3,4"); var content = await response?.Content.ReadAsStringAsync(); logger.Information(content); productList = JsonConvert.DeserializeObject>(content).ToArray(); client = await clientFactory.Create(new Uri("http://localhost:5200/"), "shopping_cart_write"); response = await client.GetAsync($"/shoppingcart/{userId}"); content = await response?.Content.ReadAsStringAsync(); logger.Information(content); var basketProducts = GetBasketProductsFromResponse(content); return View["productlist", new { ProductList = productList, BasketProducts = basketProducts }]; }); Post("/shoppingcart/{userid}", async parameters => { var productId = this.Bind(); var userId = (int) parameters.userid; var client = await clientFactory.Create(new Uri("http://localhost:5200/"), "shopping_cart_write"); var response = await client.PostAsync( $"/shoppingcart/{userId}/items", new StringContent(JsonConvert.SerializeObject(new[] { productId }), UTF8, "application/json")); var content = await response?.Content.ReadAsStringAsync(); logger.Information(content); var basketProducts = GetBasketProductsFromResponse(content); logger.Information("{@basket}", basketProducts); return View["productlist", new { ProductList = productList, BasketProducts = basketProducts }]; }); Delete("/shoppingcart/{userid}", async parameters => { var productId = this.Bind(); var userId = (int) parameters.userid; HttpClient client = await clientFactory.Create(new Uri("http://localhost:5200/"), "shopping_cart_write"); var request = new HttpRequestMessage(HttpMethod.Delete, $"/shoppingcart/{userId}/items") { Content = new StringContent(JsonConvert.SerializeObject(new[] { productId }), UTF8, "application/json") }; var response = await client.SendAsync(request); var content = await response?.Content.ReadAsStringAsync(); logger.Information(content); var basketProducts = GetBasketProductsFromResponse(content); logger.Information("{@basket}", basketProducts); return View["productlist", new { ProductList = productList, BasketProducts = basketProducts }]; }); } private List GetBasketProductsFromResponse(string responseBody) { return JsonConvert.DeserializeObject(responseBody) .Items ?.Select(item => new Product {ProductName = item.ProductName, ProductId = item.ProductCatalogueId}) ?.ToList() ?? new List(); } } public class Product { public string ProductName; public int ProductId; } public class ShoppingCart { public IEnumerable Items { get; set; } } public class ShoppingCartItem { public int ProductCatalogueId { get; set;} public string ProductName { get; set; } } } ================================================ FILE: src/Chapter12/ApiGateway/Program.cs ================================================ using System.IO; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; namespace ApiGateway { public class Program { public static void Main(string[] args) { var config = new ConfigurationBuilder() .AddEnvironmentVariables(prefix: "ASPNETCORE_") .Build(); var host = new WebHostBuilder() .UseConfiguration(config) .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter12/ApiGateway/Properties/launchSettings.json ================================================ { "profiles": { "ApiGateway": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter12/ApiGateway/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter12/ApiGateway/Startup.cs ================================================ namespace ApiGateway { using System.Threading.Tasks; using MicroserviceNET.Logging; using MicroserviceNET.Platform; using MicroserviceNET.Auth; using Microsoft.AspNetCore.Builder; using Nancy; using Nancy.Bootstrapper; using Nancy.Owin; using Nancy.TinyIoc; using Serilog; using Serilog.Events; public class Startup { public void Configure(IApplicationBuilder app) { var logger = ConfigureLogger(); app.UseStaticFiles(); app.UseOwin() .UseMonitoringAndLogging(logger, HealtCheck) .UseNancy(opt => opt.Bootstrapper = new Bootstrapper(logger)); } private ILogger ConfigureLogger() { MicroservicePlatform.Configure( tokenUrl: "http://localhost:5001/", clientName:"api_gateway", clientSecret: "secret"); return new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.ColoredConsole( LogEventLevel.Verbose, "{NewLine}{Timestamp:HH:mm:ss} [{Level}] ({CorrelationToken}) {Message}{NewLine}{Exception}") .CreateLogger(); } private static Task HealtCheck() { return Task.FromResult(true); } } public class Bootstrapper : DefaultNancyBootstrapper { private ILogger logger; public Bootstrapper(ILogger logger) { this.logger = logger; } protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) { base.ApplicationStartup(container, pipelines); container.Register(logger); container.UseHttpClientFactory(new NancyContext()); } protected override void RequestStartup( TinyIoCContainer container, IPipelines pipelines, NancyContext context) { base.RequestStartup(container, pipelines, context); container.UseHttpClientFactory(context); } } public class CustomRootPathProvider : IRootPathProvider { public string GetRootPath() { return @"C:\Users\chors_000\Documents\horsdal\code\Chapter12\ApiGateway"; } } } ================================================ FILE: src/Chapter12/ApiGateway/productlist.sshtml ================================================ MicroCommerce.NET
@Each.ProductList

@Current.ProductName

lorem ipsum

@EndEach
Shopping Cart
@Each.BasketProducts
@Current.ProductName
@EndEach
================================================ FILE: src/Chapter12/ApiGateway/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Owin": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "System.Text.Encoding": "4.0.11", "Nancy": "2.0.0-barneyrubble", "MicroserviceNET.Platform": { "target": "project" }, "Serilog.Sinks.ColoredConsole": "2.0.0-beta-700" }, "tools": { }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "configProperties": { "System.GC.Server": true } }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "ApiGateway" } } ================================================ FILE: src/Chapter12/ApiGateway/web.config ================================================ ================================================ FILE: src/Chapter12/Login/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter12/Login/Configuration/Clients.cs ================================================ namespace Login.Configuration { using System.Collections.Generic; using IdentityServer4.Models; public class Clients { public static IEnumerable Get() => new List { new Client { ClientName = "API Gateway", ClientId = "api_gateway", ClientSecrets = new List { new Secret("secret".Sha256()) }, AllowedScopes = new List { "loyalty_program_write", }, AllowedGrantTypes = GrantTypes.ClientCredentials }, new Client { ClientName = "Web Client", ClientId = "web", RedirectUris = new List { "http://localhost:5003/signin-oidc", }, PostLogoutRedirectUris = new List { "http://localhost:5003/", }, AllowedScopes = new List { "openid", "email", "profile", } } }; } } ================================================ FILE: src/Chapter12/Login/Configuration/Scopes.cs ================================================ namespace Login.Configuration { using System.Collections.Generic; using IdentityServer4.Models; public class Scopes { public static IEnumerable Get() => new[] { // standard OpenID Connect scopes StandardScopes.OpenId, StandardScopes.ProfileAlwaysInclude, StandardScopes.EmailAlwaysInclude, new Scope { Name = "loyalty_program_write", DisplayName = "Loyalty Program write access", Type = ScopeType.Resource, } }; } } ================================================ FILE: src/Chapter12/Login/Configuration/Users.cs ================================================ namespace Login.Configuration { using System.Collections.Generic; using System.Security.Claims; using IdentityModel; using IdentityServer4.Services.InMemory; static class Users { public static List Get() => new List { new InMemoryUser{Subject = "818727", Username = "alice", Password = "alice", Claims = new[] { new Claim(JwtClaimTypes.Name, "Alice Smith"), new Claim(JwtClaimTypes.GivenName, "Alice"), new Claim(JwtClaimTypes.FamilyName, "Smith"), new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.Role, "User"), new Claim(JwtClaimTypes.Id, "1", ClaimValueTypes.Integer64) } }, new InMemoryUser{Subject = "88421113", Username = "bob", Password = "bob", Claims = new[] { new Claim(JwtClaimTypes.Name, "Bob Smith"), new Claim(JwtClaimTypes.GivenName, "Bob"), new Claim(JwtClaimTypes.FamilyName, "Smith"), new Claim(JwtClaimTypes.Email, "BobSmith@email.com"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.Role, "User"), new Claim(JwtClaimTypes.Id, "2", ClaimValueTypes.Integer64) } } }; } } ================================================ FILE: src/Chapter12/Login/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter12/Login/Login.xproj ================================================  14.0.25123 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) f416d0b1-71ee-400e-91e2-fc2b047da208 Login .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter12/Login/Program.cs ================================================ namespace Login { using System.IO; using Microsoft.AspNetCore.Hosting; public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .UseUrls("http://localhost:5001") .Build(); host.Run(); } } } ================================================ FILE: src/Chapter12/Login/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "IdentityServer": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter12/Login/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter12/Login/Startup.cs ================================================ namespace Login { using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Configuration; public class Startup { private readonly IHostingEnvironment environment; public Startup(IHostingEnvironment env) { this.environment = env; } public void ConfigureServices(IServiceCollection services) { var cert = new X509Certificate2(Path.Combine(this.environment.ContentRootPath, "idsrv3test.pfx"), "idsrv3test"); services.AddSingleton(); var builder = services .AddIdentityServer() .SetSigningCredential(cert) .AddInMemoryClients(Clients.Get()) .AddInMemoryScopes(Scopes.Get()) .AddInMemoryUsers(Users.Get()); services.AddMvc(); } public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(LogLevel.Trace); loggerFactory.AddDebug(LogLevel.Trace); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationScheme = "Temp", AutomaticAuthenticate = false, AutomaticChallenge = false }); app.UseIdentityServer(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); } } } ================================================ FILE: src/Chapter12/Login/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0", "IdentityServer4": "1.0.0-beta5", "SeriLog": "2.0.0-rc-600" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "IdentityServer" } } ================================================ FILE: src/Chapter12/Login/web.config ================================================ ================================================ FILE: src/Chapter12/ProductCatalog/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter12/ProductCatalog/Dockerfile ================================================ FROM microsoft/dotnet:latest COPY . /app WORKDIR /app RUN ["dotnet", "restore"] RUN ["dotnet", "build"] EXPOSE 5000/tcp ENTRYPOINT ["dotnet", "run", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter12/ProductCatalog/ProductsModule.cs ================================================ namespace ProductCatalog { using System; using System.Collections.Generic; using System.Linq; using Nancy; public class ProductsModule : NancyModule { public ProductsModule(ProductStore productStore) : base("/products") { Get("", _ => { string productIdsString = this.Request.Query.productIds; var productIds = ParseProductIdsFromQueryString(productIdsString); var products = productStore.GetProductsByIds(productIds); return this .Negotiate .WithModel(products) .WithHeader("cache-control", "max-age:86400"); }); } private static IEnumerable ParseProductIdsFromQueryString(string productIdsString) { return productIdsString.Split(',').Select(s => s.Replace("[", "").Replace("]", "")).Select(int.Parse); } } public interface ProductStore { IEnumerable GetProductsByIds(IEnumerable productIds); } public class StaticProductStore : ProductStore { public IEnumerable GetProductsByIds(IEnumerable productIds) { return productIds.Select(id => new ProductCatalogProduct(id, "Product no. " + id, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", new Money())); } } public class ProductCatalogProduct { public ProductCatalogProduct(int productId, string productName, string description, Money price) { this.ProductId = productId.ToString(); this.ProductName = productName; this.ProductDescription = description; this.Price = price; } public string ProductId { get; private set; } public string ProductName { get; private set; } public string ProductDescription { get; private set; } public Money Price { get; private set; } } public class Money { } } ================================================ FILE: src/Chapter12/ProductCatalog/Program.cs ================================================ namespace ProductCatalog { using System.IO; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; public class Program { public static void Main(string[] args) { var config = new ConfigurationBuilder() .AddEnvironmentVariables(prefix: "ASPNETCORE_") .Build(); var host = new WebHostBuilder() .UseConfiguration(config) .UseKestrel() .UseUrls("http://localhost:5100") .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter12/ProductCatalog/Properties/launchSettings.json ================================================ { "profiles": { "ProductCatalog": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter12/ProductCatalog/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter12/ProductCatalog/Startup.cs ================================================ namespace ProductCatalog { using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; using Nancy.Owin; public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseOwin().UseNancy(); } } } ================================================ FILE: src/Chapter12/ProductCatalog/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Owin": "1.0.0", "Nancy": "2.0.0-barneyrubble" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "configProperties": { "System.GC.Server": true } }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "ProductCatalog" } } ================================================ FILE: src/Chapter12/ProductCatalog/web.config ================================================ ================================================ FILE: src/Chapter12/ShoppingCart/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/Chapter12/ShoppingCart/.vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "name": ".NET Core Launch (console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", "args": [], "cwd": "${workspaceRoot}", "stopAtEntry": false }, { "name": ".NET Core Launch (web)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/ShoppingCart.dll", "args": [], "cwd": "${workspaceRoot}", "stopAtEntry": false, "launchBrowser": { "enabled": true, "args": "${auto-detect-url}", "windows": { "command": "cmd.exe", "args": "/C start ${auto-detect-url}" }, "osx": { "command": "open" }, "linux": { "command": "xdg-open" } } }, { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", "processName": "" } ] } ================================================ FILE: src/Chapter12/ShoppingCart/.vscode/tasks.json ================================================ { "version": "0.1.0", "command": "dotnet", "isShellCommand": true, "args": [], "tasks": [ { "taskName": "build", "args": [], "isBuildCommand": true, "problemMatcher": "$msCompile" } ] } ================================================ FILE: src/Chapter12/ShoppingCart/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/Chapter12/ShoppingCart/EventFeed/Event.cs ================================================ namespace ShoppingCart.EventFeed { using System; public struct Event { public long SequenceNumber { get; } public DateTimeOffset OccuredAt { get; } public string Name { get; } public object Content { get; } public Event( long sequenceNumber, DateTimeOffset occuredAt, string name, object content) { this.SequenceNumber = sequenceNumber; this.OccuredAt = occuredAt; this.Name = name; this.Content = content; } } } ================================================ FILE: src/Chapter12/ShoppingCart/EventFeed/EventStore.cs ================================================ namespace ShoppingCart.EventFeed { using System; using System.Text; using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; using System.Threading.Tasks; using Dapper; using Newtonsoft.Json; #if net461 using global::EventStore.ClientAPI; #endif public class EventStore : IEventStore { private static long currentSequenceNumber = 0; private static readonly IList database = new List(); #if net461 private const string connectionString = "ConnectTo=discover://admin:changeit@127.0.0.1:2112/"; private IEventStoreConnection connection = EventStoreConnection.Create(connectionString); public async Task Raise(string eventName, object content) { await connection.ConnectAsync().ConfigureAwait(false); var contentJson = JsonConvert.SerializeObject(content); var metaDataJson = JsonConvert.SerializeObject(new EventMetadata { OccuredAt = DateTimeOffset.Now, EventName = eventName }); var eventData = new EventData( Guid.NewGuid(), "ShoppingCartEvent", isJson: true, data: Encoding.UTF8.GetBytes(contentJson), metadata: Encoding.UTF8.GetBytes(metaDataJson) ); await connection.AppendToStreamAsync( "ShoppingCart", ExpectedVersion.Any, eventData); } public async Task> GetEvents( long firstEventSequenceNumber, long lastEventSequenceNumber) { await connection.ConnectAsync().ConfigureAwait(false); var result = await connection.ReadStreamEventsForwardAsync( "ShoppingCart", start:(int) firstEventSequenceNumber, count: (int) (lastEventSequenceNumber - firstEventSequenceNumber), resolveLinkTos: false).ConfigureAwait(false); return result.Events .Select(ev => new { Content = JsonConvert.DeserializeObject( Encoding.UTF8.GetString(ev.Event.Data)), Metadata = JsonConvert.DeserializeObject( Encoding.UTF8.GetString(ev.Event.Data)) }) .Select((ev, i) => new Event( i + firstEventSequenceNumber, ev.Metadata.OccuredAt, ev.Metadata.EventName, ev.Content)); } private class EventMetadata { public DateTimeOffset OccuredAt { get; set; } public string EventName { get; set; } } #else private string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=ShoppingCart;Integrated Security=True"; private const string writeEventSql = @"insert into EventStore(Name, OccuredAt, Content) values (@Name, @OccuredAt, @Content)"; public Task Raise(string eventName, object content) { var jsonContent = JsonConvert.SerializeObject(content); using (var conn = new SqlConnection(connectionString)) { return conn.ExecuteAsync( writeEventSql, new { Name = eventName, OccuredAt = DateTimeOffset.Now, Content = jsonContent }); } } private const string readEventsSql = @"select * from EventStore where ID >= @Start and ID <= @End"; public async Task> GetEvents( long firstEventSequenceNumber, long lastEventSequenceNumber) { using (var conn = new SqlConnection(connectionString)) { return (await conn.QueryAsync( readEventsSql, new { Start = firstEventSequenceNumber, End = lastEventSequenceNumber }).ConfigureAwait(false)) .Select(row => { var content = JsonConvert.DeserializeObject(row.Content); return new Event(row.ID, row.OccuredAt, row.Name, content); }); } } #endif } } ================================================ FILE: src/Chapter12/ShoppingCart/EventFeed/EventsFeedModule.cs ================================================ namespace ShoppingCart.EventFeed { using Nancy; public class EventsFeedModule : NancyModule { public EventsFeedModule(IEventStore eventStore) : base("/events") { Get("/", _ => { long firstEventSequenceNumber, lastEventSequenceNumber; if (!long.TryParse(this.Request.Query.start.Value, out firstEventSequenceNumber)) firstEventSequenceNumber = 0; if (!long.TryParse(this.Request.Query.end.Value, out lastEventSequenceNumber)) lastEventSequenceNumber = long.MaxValue; return eventStore.GetEvents( firstEventSequenceNumber, lastEventSequenceNumber); }); } } } ================================================ FILE: src/Chapter12/ShoppingCart/EventFeed/IEventStore.cs ================================================ namespace ShoppingCart.EventFeed { using System.Collections.Generic; using System.Threading.Tasks; public interface IEventStore { Task> GetEvents(long firstEventSequenceNumber, long lastEventSequenceNumber); Task Raise(string eventName, object content); } } ================================================ FILE: src/Chapter12/ShoppingCart/ICache.cs ================================================ namespace ShoppingCart { using System; using System.Collections.Concurrent; using System.Collections.Generic; public interface ICache { void Add(string key, object value, TimeSpan ttl); object Get(string productsResource); } public class Cache : ICache { private static IDictionary> cache = new ConcurrentDictionary>(); public void Add(string key, object value, TimeSpan ttl) { cache[key] = Tuple.Create(DateTimeOffset.UtcNow.Add(ttl), value); } public object Get(string productsResource) { Tuple value; if (cache.TryGetValue(productsResource, out value) && value.Item1 > DateTimeOffset.UtcNow) return value; cache.Remove(productsResource); return null; } } } ================================================ FILE: src/Chapter12/ShoppingCart/IProductCatalogueClient.cs ================================================ namespace ShoppingCart { using System.Collections.Generic; using System.Threading.Tasks; using ShoppingCart; public interface IProductCatalogueClient { Task> GetShoppingCartItems(int[] productCatalogueIds); } } ================================================ FILE: src/Chapter12/ShoppingCart/ProductCatalogueClient.cs ================================================ namespace ShoppingCart { using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Net.Http; using System.Threading; using Newtonsoft.Json; using Polly; using ShoppingCart; using System.Net.Http.Headers; public class ProductCatalogueClient : IProductCatalogueClient { private static Policy exponentialRetryPolicy = Policy .Handle() .WaitAndRetryAsync( 3, attempt => TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt)), (ex, _) => Console.WriteLine(ex.ToString())); private static string productCatalogueBaseUrl = @"http://private-05cc8-chapter2productcataloguemicroservice.apiary-mock.com"; private static string getProductPathTemplate = "/products?productIds=[{0}]"; private readonly ICache cache; public ProductCatalogueClient(ICache cache) { this.cache = cache; } public Task> GetShoppingCartItems(int[] productCatalogueIds) => exponentialRetryPolicy .ExecuteAsync(() => GetItemsFromCatalogueService(productCatalogueIds)); private async Task> GetItemsFromCatalogueService(int[] productCatalogueIds) { var response = await RequestProductFromProductCatalogue(productCatalogueIds).ConfigureAwait(false); return await ConvertToShoppingCartItems(response).ConfigureAwait(false); } private async Task RequestProductFromProductCatalogue(int[] productCatalogueIds) { var productsResource = string.Format( getProductPathTemplate, string.Join(",", productCatalogueIds)); var response = this.cache.Get(productsResource) as HttpResponseMessage; if (response == null) { using (var httpClient = new HttpClient()) { httpClient.BaseAddress = new Uri(productCatalogueBaseUrl); response = await httpClient.GetAsync(productsResource).ConfigureAwait(false); AddToCache(productsResource, response); } } return response; } private void AddToCache(string resource, HttpResponseMessage response) { var cacheHeader = response .Headers .FirstOrDefault(h => h.Key == "cache-control"); if (string.IsNullOrEmpty(cacheHeader.Key)) return; var maxAge = CacheControlHeaderValue.Parse(cacheHeader.Value.ToString()) .MaxAge; if (maxAge.HasValue) this.cache.Add(key: resource, value: response, ttl: maxAge.Value); } private static async Task> ConvertToShoppingCartItems(HttpResponseMessage response) { response.EnsureSuccessStatusCode(); var products = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync().ConfigureAwait(false)); return products .Select(p => new ShoppingCartItem( int.Parse(p.ProductId), p.ProductName, p.ProductDescription, p.Price )); } private class ProductCatalogueProduct { public string ProductId { get; set; } public string ProductName { get; set; } public string ProductDescription { get; set; } public Money Price { get; set; } } } } ================================================ FILE: src/Chapter12/ShoppingCart/Program.cs ================================================ using System.IO; using Microsoft.AspNetCore.Hosting; namespace ShoppingCart { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseUrls("http://localhost:5200") .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/Chapter12/ShoppingCart/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "ShoppingCart": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/Chapter12/ShoppingCart/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/Chapter12/ShoppingCart/ShoppingCart/IShoppingCartStore.cs ================================================ namespace ShoppingCart.ShoppingCart { using System.Threading.Tasks; public interface IShoppingCartStore { Task Get(int userId); Task Save(ShoppingCart shoppingCart); } } ================================================ FILE: src/Chapter12/ShoppingCart/ShoppingCart/ShoppingCart.cs ================================================ namespace ShoppingCart.ShoppingCart { using System.Collections.Generic; using System.Linq; using global::ShoppingCart.EventFeed; public class ShoppingCart { private HashSet items = new HashSet(); public int UserId { get; } public IEnumerable Items { get { return items; } } public ShoppingCart(int userId) { this.UserId = userId; } public ShoppingCart(int userId, IEnumerable items) { this.UserId = userId; foreach (var item in items) { this.items.Add(item); } } public void AddItems( IEnumerable shoppingCartItems, IEventStore eventStore) { foreach (var item in shoppingCartItems) if (this.items.Add(item)) eventStore.Raise( "ShoppingCartItemAdded", new { UserId, item }); } public void RemoveItems( int[] productCatalogueIds, IEventStore eventStore) { items.RemoveWhere(i => productCatalogueIds.Contains(i.ProductCatalogueId)); } } public class ShoppingCartItem { public int ProductCatalogueId { get; } public string ProductName { get; } public string Desscription { get; } public Money Price { get; } public ShoppingCartItem( int productCatalogueId, string productName, string description, Money price) { this.ProductCatalogueId = productCatalogueId; this.ProductName = productName; this.Desscription = description; this.Price = price; } public override bool Equals(object obj) { if (obj == null || GetType() != obj.GetType()) { return false; } var that = obj as ShoppingCartItem; return this.ProductCatalogueId.Equals(that.ProductCatalogueId); } // override object.GetHashCode public override int GetHashCode() { return this.ProductCatalogueId.GetHashCode(); } } public class Money { public string Currency { get; } public decimal Amount { get; } public Money(string currency, decimal amount) { this.Currency = currency; this.Amount = amount; } } } ================================================ FILE: src/Chapter12/ShoppingCart/ShoppingCart/ShoppingCartModule.cs ================================================ namespace ShoppingCart.ShoppingCart { using EventFeed; using Nancy; using Nancy.ModelBinding; public class ShoppingCartModule : NancyModule { public ShoppingCartModule( IShoppingCartStore shoppingCartStore, IProductCatalogueClient productCatalogue, IEventStore eventStore) : base("/shoppingcart") { Get("/{userid:int}", parameters => { var userId = (int) parameters.userid; return shoppingCartStore.Get(userId); }); Post("/{userid:int}/items", async parameters => { var productCatalogueIds = this.Bind(); var userId = (int) parameters.userid; var shoppingCart = await shoppingCartStore.Get(userId).ConfigureAwait(false); var shoppingCartItems = await productCatalogue.GetShoppingCartItems(productCatalogueIds).ConfigureAwait(false); shoppingCart.AddItems(shoppingCartItems, eventStore); await shoppingCartStore.Save(shoppingCart); return shoppingCart; }); Delete("/{userid:int}/items", async parameters => { var productCatalogueIds = this.Bind(); var userId = (int)parameters.userid; var shoppingCart = await shoppingCartStore.Get(userId).ConfigureAwait(false); shoppingCart.RemoveItems(productCatalogueIds, eventStore); await shoppingCartStore.Save(shoppingCart); return shoppingCart; }); } } } ================================================ FILE: src/Chapter12/ShoppingCart/ShoppingCart/ShoppingCartStore.cs ================================================ namespace ShoppingCart.ShoppingCart { using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Threading.Tasks; using Dapper; public class ShoppingCartStore : IShoppingCartStore { private string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=ShoppingCart; Integrated Security=True"; private const string readItemsSql = @"select * from ShoppingCart, ShoppingCartItems where ShoppingCartItems.ShoppingCartId = ID and ShoppingCart.UserId=@UserId"; public async Task Get(int userId) { using (var conn = new SqlConnection(connectionString)) { var items = await conn.QueryAsync( readItemsSql, new { UserId = userId }); return new ShoppingCart(userId, items); } } private const string deleteAllForShoppingCartSql= @"delete item from ShoppingCartItems item inner join ShoppingCart cart on item.ShoppingCartId = cart.ID and cart.UserId=@UserId"; private const string addAllForShoppingCartSql= @"insert into ShoppingCartItems (ShoppingCartId, ProductCatalogId, ProductName, ProductDescription, Amount, Currency) values (@ShoppingCartId, @ProductCatalogId, @ProductName,v @ProductDescription, @Amount, @Currency)"; public async Task Save(ShoppingCart shoppingCart) { using (var conn = new SqlConnection(connectionString)) using (var tx = conn.BeginTransaction()) { await conn.ExecuteAsync( deleteAllForShoppingCartSql, new { UserId = shoppingCart.UserId }, tx).ConfigureAwait(false); await conn.ExecuteAsync( addAllForShoppingCartSql, shoppingCart.Items, tx).ConfigureAwait(false); } } } } ================================================ FILE: src/Chapter12/ShoppingCart/ShoppingCart.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 10e4f601-c16b-4936-a7b4-d32d799318d1 ShoppingCart .\obj .\bin\ 2.0 ================================================ FILE: src/Chapter12/ShoppingCart/Startup.cs ================================================ namespace ShoppingCart { using Microsoft.AspNetCore.Builder; using Nancy; using Nancy.Configuration; using Nancy.Owin; public class Startup { public void Configure(IApplicationBuilder app) { app.UseOwin().UseNancy(opt => opt.Bootstrapper = new TracingBootstrapper()); } } public class TracingBootstrapper : Nancy.DefaultNancyBootstrapper { public override void Configure(INancyEnvironment env) { env.Tracing(enabled: true, displayErrorTraces: true); } } } ================================================ FILE: src/Chapter12/ShoppingCart/database-scripts/create-shopping-cart-db.sql ================================================ CREATE DATABASE ShoppingCart GO USE [ShoppingCart] GO CREATE TABLE [dbo].[ShoppingCart]( [ID] int IDENTITY(1,1) PRIMARY KEY, [UserId] [bigint] NOT NULL, CONSTRAINT ShoppingCartUnique UNIQUE([ID], [UserID]) ) GO CREATE INDEX ShoppingCart_UserId ON [dbo].[ShoppingCart] (UserId) GO CREATE TABLE [dbo].[ShoppingCartItems]( [ID] int IDENTITY(1,1) PRIMARY KEY, [ShoppingCartId] [int] NOT NULL, [ProductCatalogId] [bigint] NOT NULL, [ProductName] [nvarchar](100) NOT NULL, [ProductDescription] [nvarchar](500) NULL, [Amount] [int] NOT NULL, [Currency] [nvarchar](5) NOT NULL ) GO ALTER TABLE [dbo].[ShoppingCartItems] WITH CHECK ADD CONSTRAINT [FK_ShoppingCart] FOREIGN KEY([ShoppingCartId]) REFERENCES [dbo].[ShoppingCart] ([Id]) GO ALTER TABLE [dbo].[ShoppingCartItems] CHECK CONSTRAINT [FK_ShoppingCart] GO CREATE INDEX ShoppingCartItems_ShoppingCartId ON [dbo].[ShoppingCartItems] (ShoppingCartId) GO CREATE TABLE [dbo].[EventStore]( [ID] int IDENTITY(1,1) PRIMARY KEY, [Name] [nvarchar](100) NOT NULL, [OccuredAt] [datetimeoffset] NOT NULL, [Content][nvarchar](max) NOT NULL ) GO ================================================ FILE: src/Chapter12/ShoppingCart/project.json ================================================ { "dependencies": { "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Owin": "1.0.0", "Nancy": "2.0.0-barneyrubble", "Polly": "4.2.1", "Dapper": "1.50.0-rc2a" }, "runtimes": { "win10-x64": "" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ], "dependencies": { "System.Net.Http": "4.0.1" } }, "net461": { "buildOptions": {"define": ["net461"]}, "frameworkAssemblies": { "System.Net.Http": "4.0.0.0", "System.Runtime": "4.0.20.0" }, "dependencies": { "EventStore.Client": "3.3.1", "Microsoft.CSharp": "4.0.0" } } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "HelloMicroservices" } } ================================================ FILE: src/Chapter12/ShoppingCart/web.config ================================================ ================================================ FILE: src/Chapter12/start-app.ps1 ================================================ start-process dotnet -ArgumentList run -WorkingDirectory ./Login start-process dotnet -ArgumentList run -WorkingDirectory ./ProductCatalog start-process dotnet -ArgumentList run -WorkingDirectory ./ShoppingCart start-process dotnet -ArgumentList run -WorkingDirectory ./ApiGateway ================================================ FILE: src/chapter08/OwinMiddlewareTests/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/chapter08/OwinMiddlewareTests/LibOwin.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. // Copyright (c) Damian Hickey. All rights reserved. See License.txt in the project root for license information. // https://github.com/damianh/LibOwin // Modifying this file may result in difficulties when upgrading the package. // All types are internal. Add a LIBOWIN_PUBLIC compilation symbol to make them public. namespace LibOwin.Infrastructure { using System; using System.Collections; using System.Collections.Generic; using System.Linq; internal static partial class Constants { internal const string Https = "HTTPS"; internal const string HttpDateFormat = "r"; internal static partial class Headers { internal const string ContentType = "Content-Type"; internal const string CacheControl = "Cache-Control"; internal const string MediaType = "Media-Type"; internal const string Accept = "Accept"; internal const string Host = "Host"; internal const string ETag = "ETag"; internal const string Location = "Location"; internal const string ContentLength = "Content-Length"; internal const string SetCookie = "Set-Cookie"; internal const string Expires = "Expires"; } } internal struct HeaderSegment : IEquatable { private readonly StringSegment _formatting; private readonly StringSegment _data; // // Initializes a new instance of the class. // public HeaderSegment(StringSegment formatting, StringSegment data) { _formatting = formatting; _data = data; } public StringSegment Formatting { get { return _formatting; } } public StringSegment Data { get { return _data; } } #region Equality members public bool Equals(HeaderSegment other) { return _formatting.Equals(other._formatting) && _data.Equals(other._data); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegment && Equals((HeaderSegment)obj); } public override int GetHashCode() { unchecked { return (_formatting.GetHashCode() * 397) ^ _data.GetHashCode(); } } public static bool operator ==(HeaderSegment left, HeaderSegment right) { return left.Equals(right); } public static bool operator !=(HeaderSegment left, HeaderSegment right) { return !left.Equals(right); } #endregion } internal struct HeaderSegmentCollection : IEnumerable, IEquatable { private readonly string[] _headers; public HeaderSegmentCollection(string[] headers) { _headers = headers; } #region Equality members public bool Equals(HeaderSegmentCollection other) { return Equals(_headers, other._headers); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegmentCollection && Equals((HeaderSegmentCollection)obj); } public override int GetHashCode() { return (_headers != null ? _headers.GetHashCode() : 0); } public static bool operator ==(HeaderSegmentCollection left, HeaderSegmentCollection right) { return left.Equals(right); } public static bool operator !=(HeaderSegmentCollection left, HeaderSegmentCollection right) { return !left.Equals(right); } #endregion public Enumerator GetEnumerator() { return new Enumerator(_headers); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } internal struct Enumerator : IEnumerator { private readonly string[] _headers; private int _index; private string _header; private int _headerLength; private int _offset; private int _leadingStart; private int _leadingEnd; private int _valueStart; private int _valueEnd; private int _trailingStart; private Mode _mode; private static readonly string[] NoHeaders = new string[0]; public Enumerator(string[] headers) { _headers = headers ?? NoHeaders; _header = string.Empty; _headerLength = -1; _index = -1; _offset = -1; _leadingStart = -1; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; _mode = Mode.Leading; } private enum Mode { Leading, Value, ValueQuoted, Trailing, Produce, } private enum Attr { Value, Quote, Delimiter, Whitespace } public HeaderSegment Current { get { return new HeaderSegment( new StringSegment(_header, _leadingStart, _leadingEnd - _leadingStart), new StringSegment(_header, _valueStart, _valueEnd - _valueStart)); } } object IEnumerator.Current { get { return Current; } } public void Dispose() { } public bool MoveNext() { while (true) { if (_mode == Mode.Produce) { _leadingStart = _trailingStart; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; if (_offset == _headerLength && _leadingStart != -1 && _leadingStart != _offset) { // Also produce trailing whitespace _leadingEnd = _offset; return true; } _mode = Mode.Leading; } // if end of a string if (_offset == _headerLength) { ++_index; _offset = -1; _leadingStart = 0; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; // if that was the last string if (_index == _headers.Length) { // no more move nexts return false; } // grab the next string _header = _headers[_index] ?? string.Empty; _headerLength = _header.Length; } while (true) { ++_offset; char ch = _offset == _headerLength ? (char)0 : _header[_offset]; // todo - array of attrs Attr attr = char.IsWhiteSpace(ch) ? Attr.Whitespace : ch == '\"' ? Attr.Quote : (ch == ',' || ch == (char)0) ? Attr.Delimiter : Attr.Value; switch (_mode) { case Mode.Leading: switch (attr) { case Attr.Delimiter: _leadingEnd = _offset; _mode = Mode.Produce; break; case Attr.Quote: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.ValueQuoted; break; case Attr.Value: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; case Mode.Value: switch (attr) { case Attr.Quote: _mode = Mode.ValueQuoted; break; case Attr.Delimiter: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; break; case Attr.Value: // more break; case Attr.Whitespace: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Trailing; break; } break; case Mode.ValueQuoted: switch (attr) { case Attr.Quote: _mode = Mode.Value; break; case Attr.Delimiter: if (ch == (char)0) { _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; } break; case Attr.Value: case Attr.Whitespace: // more break; } break; case Mode.Trailing: switch (attr) { case Attr.Delimiter: _mode = Mode.Produce; break; case Attr.Quote: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.ValueQuoted; break; case Attr.Value: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; } if (_mode == Mode.Produce) { return true; } } } } public void Reset() { _index = 0; _offset = 0; _leadingStart = 0; _leadingEnd = 0; _valueStart = 0; _valueEnd = 0; } } } internal struct StringSegment : IEquatable { private readonly string _buffer; private readonly int _offset; private readonly int _count; // // Initializes a new instance of the class. // public StringSegment(string buffer, int offset, int count) { _buffer = buffer; _offset = offset; _count = count; } public string Buffer { get { return _buffer; } } public int Offset { get { return _offset; } } public int Count { get { return _count; } } public string Value { get { return _offset == -1 ? null : _buffer.Substring(_offset, _count); } } public bool HasValue { get { return _offset != -1 && _count != 0 && _buffer != null; } } #region Equality members public bool Equals(StringSegment other) { return string.Equals(_buffer, other._buffer) && _offset == other._offset && _count == other._count; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is StringSegment && Equals((StringSegment)obj); } public override int GetHashCode() { unchecked { int hashCode = (_buffer != null ? _buffer.GetHashCode() : 0); hashCode = (hashCode * 397) ^ _offset; hashCode = (hashCode * 397) ^ _count; return hashCode; } } public static bool operator ==(StringSegment left, StringSegment right) { return left.Equals(right); } public static bool operator !=(StringSegment left, StringSegment right) { return !left.Equals(right); } #endregion public bool StartsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public bool EndsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset + _count - textLength, text, 0, textLength, comparisonType) == 0; } public bool Equals(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count != textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public string Substring(int offset, int length) { return _buffer.Substring(_offset + offset, length); } public StringSegment Subsegment(int offset, int length) { return new StringSegment(_buffer, _offset + offset, length); } public override string ToString() { return Value ?? string.Empty; } } internal static partial class OwinHelpers { private static readonly Action AddCookieCallback = (name, value, state) => { var dictionary = (IDictionary)state; if (!dictionary.ContainsKey(name)) { dictionary.Add(name, value); } }; private static readonly char[] SemicolonAndComma = new[] { ';', ',' }; internal static IDictionary GetCookies(IOwinRequest request) { var cookies = request.Get>("Microsoft.Owin.Cookies#dictionary"); if (cookies == null) { cookies = new Dictionary(StringComparer.Ordinal); request.Set("Microsoft.Owin.Cookies#dictionary", cookies); } string text = GetHeader(request.Headers, "Cookie"); if (request.Get("Microsoft.Owin.Cookies#text") != text) { cookies.Clear(); ParseDelimited(text, SemicolonAndComma, AddCookieCallback, cookies); request.Set("Microsoft.Owin.Cookies#text", text); } return cookies; } internal static void ParseDelimited(string text, char[] delimiters, Action callback, object state) { int textLength = text.Length; int equalIndex = text.IndexOf('='); if (equalIndex == -1) { equalIndex = textLength; } int scanIndex = 0; while (scanIndex < textLength) { int delimiterIndex = text.IndexOfAny(delimiters, scanIndex); if (delimiterIndex == -1) { delimiterIndex = textLength; } if (equalIndex < delimiterIndex) { while (scanIndex != equalIndex && char.IsWhiteSpace(text[scanIndex])) { ++scanIndex; } string name = text.Substring(scanIndex, equalIndex - scanIndex); string value = text.Substring(equalIndex + 1, delimiterIndex - equalIndex - 1); callback( Uri.UnescapeDataString(name.Replace('+', ' ')), Uri.UnescapeDataString(value.Replace('+', ' ')), state); equalIndex = text.IndexOf('=', delimiterIndex); if (equalIndex == -1) { equalIndex = textLength; } } scanIndex = delimiterIndex + 1; } } } internal static partial class OwinHelpers { public static string GetHeader(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : string.Join(",", values); } public static IEnumerable GetHeaderSplit(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : GetHeaderSplitImplementation(values); } private static IEnumerable GetHeaderSplitImplementation(string[] values) { foreach (var segment in new HeaderSegmentCollection(values)) { if (segment.Data.HasValue) { yield return DeQuote(segment.Data.Value); } } } public static string[] GetHeaderUnmodified(IDictionary headers, string key) { if (headers == null) { throw new ArgumentNullException("headers"); } string[] values; return headers.TryGetValue(key, out values) ? values : null; } public static void SetHeader(IDictionary headers, string key, string value) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (string.IsNullOrWhiteSpace(value)) { headers.Remove(key); } else { headers[key] = new[] { value }; } } public static void SetHeaderJoined(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = new[] { string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } // Quote items that contain comas and are not already quoted. private static string QuoteIfNeeded(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Contains(',')) { if (value[0] != '"' || value[value.Length - 1] != '"') { value = '"' + value + '"'; } } return value; } private static string DeQuote(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"') { value = value.Substring(1, value.Length - 2); } return value; } public static void SetHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = values; } } public static void SetHeaderUnmodified(IDictionary headers, string key, IEnumerable values) { if (headers == null) { throw new ArgumentNullException("headers"); } headers[key] = values.ToArray(); } public static void AppendHeader(IDictionary headers, string key, string values) { if (string.IsNullOrWhiteSpace(values)) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeader(headers, key, values); } else { headers[key] = new[] { existing + "," + values }; } } public static void AppendHeaderJoined(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeaderJoined(headers, key, values); } else { headers[key] = new[] { existing + "," + string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } public static void AppendHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string[] existing = GetHeaderUnmodified(headers, key); if (existing == null) { SetHeaderUnmodified(headers, key, values); } else { SetHeaderUnmodified(headers, key, existing.Concat(values)); } } } internal static partial class OwinHelpers { private static readonly Action AppendItemCallback = (name, value, state) => { var dictionary = (IDictionary>)state; List existing; if (!dictionary.TryGetValue(name, out existing)) { dictionary.Add(name, new List(1) { value }); } else { existing.Add(value); } }; private static readonly char[] AmpersandAndSemicolon = new[] { '&', ';' }; internal static IDictionary GetQuery(IOwinRequest request) { var query = request.Get>("Microsoft.Owin.Query#dictionary"); if (query == null) { query = new Dictionary(StringComparer.OrdinalIgnoreCase); request.Set("Microsoft.Owin.Query#dictionary", query); } string text = request.QueryString.Value; if (request.Get("Microsoft.Owin.Query#text") != text) { query.Clear(); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, AmpersandAndSemicolon, AppendItemCallback, accumulator); foreach (var kv in accumulator) { query.Add(kv.Key, kv.Value.ToArray()); } request.Set("Microsoft.Owin.Query#text", text); } return query; } internal static IFormCollection GetForm(string text) { IDictionary form = new Dictionary(StringComparer.OrdinalIgnoreCase); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, new[] { '&' }, AppendItemCallback, accumulator); foreach (var kv in accumulator) { form.Add(kv.Key, kv.Value.ToArray()); } return new FormCollection(form); } internal static string GetJoinedValue(IDictionary store, string key) { string[] values = GetUnmodifiedValues(store, key); return values == null ? null : string.Join(",", values); } internal static string[] GetUnmodifiedValues(IDictionary store, string key) { if (store == null) { throw new ArgumentNullException("store"); } string[] values; return store.TryGetValue(key, out values) ? values : null; } } internal static partial class OwinHelpers { internal static string GetHost(IOwinRequest request) { IHeaderDictionary headers = request.Headers; string host = GetHeader(headers, "Host"); if (!string.IsNullOrWhiteSpace(host)) { return host; } string localIpAddress = request.LocalIpAddress ?? "localhost"; var localPort = request.Get(OwinConstants.CommonKeys.LocalPort); return string.IsNullOrWhiteSpace(localPort) ? localIpAddress : (localIpAddress + ":" + localPort); } } } namespace LibOwin { using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Text; using System.Threading; using System.Threading.Tasks; using LibOwin.Infrastructure; #if LIBOWIN_PUBLIC public partial class CookieOptions { } public partial class FormCollection { } public partial class HeaderDictionary { } public partial struct HostString { } public partial interface IFormCollection { } public partial interface IHeaderDictionary { } public partial interface IOwinContext { } public partial interface IOwinRequest { } public partial interface IOwinResponse { } public partial interface IReadableStringCollection { } public partial class OwinContext { } public partial class OwinRequest { } public partial class OwinResponse { } public partial struct PathString { } public partial struct QueryString { } public partial class ReadableStringCollection { } public partial class RequestCookieCollection { } public partial class ResponseCookieCollection { } public partial class IOwinResponseExtension { } #endif /// /// Options used to create a new cookie. /// partial class CookieOptions { /// /// Creates a default cookie with a path of '/'. /// public CookieOptions() { Path = "/"; } /// /// Gets or sets the domain to associate the cookie with. /// /// The domain to associate the cookie with. public string Domain { get; set; } /// /// Gets or sets the cookie path. /// /// The cookie path. public string Path { get; set; } /// /// Gets or sets the expiration date and time for the cookie. /// /// The expiration date and time for the cookie. public DateTime? Expires { get; set; } /// /// Gets or sets a value that indicates whether to transmit the cookie using Secure Sockets Layer (SSL)—that is, over HTTPS only. /// /// true to transmit the cookie only over an SSL connection (HTTPS); otherwise, false. public bool Secure { get; set; } /// /// Gets or sets a value that indicates whether a cookie is accessible by client-side script. /// /// true if a cookie is accessible by client-side script; otherwise, false. public bool HttpOnly { get; set; } } /// /// Contains the parsed form values. /// partial class FormCollection : ReadableStringCollection, IFormCollection { /// /// Initializes a new instance of the class. /// /// The store for the form. public FormCollection(IDictionary store) : base(store) {} } /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial class HeaderDictionary : IHeaderDictionary { /// /// Initializes a new instance of the class. /// /// The underlying data store. public HeaderDictionary(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Gets an that contains the keys in the ;. /// /// An that contains the keys in the . public ICollection Keys { get { return Store.Keys; } } /// /// /// public ICollection Values { get { return Store.Values; } } /// /// Gets the number of elements contained in the ;. /// /// The number of elements contained in the . public int Count { get { return Store.Count; } } /// /// Gets a value that indicates whether the is in read-only mode. /// /// true if the is in read-only mode; otherwise, false. public bool IsReadOnly { get { return Store.IsReadOnly; } } /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string this[string key] { get { return Get(key); } set { Set(key, value); } } /// /// Throws KeyNotFoundException if the key is not present. /// /// The header name. /// string[] IDictionary.this[string key] { get { return Store[key]; } set { Store[key] = value; } } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Get the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string Get(string key) { return OwinHelpers.GetHeader(Store, key); } /// /// Get the associated values from the collection without modification. /// /// The header name. /// the associated value from the collection without modification, or null if the key is not present. public IList GetValues(string key) { return OwinHelpers.GetHeaderUnmodified(Store, key); } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. public IList GetCommaSeparatedValues(string key) { IEnumerable values = OwinHelpers.GetHeaderSplit(Store, key); return values == null ? null : values.ToList(); } /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. public void Append(string key, string value) { OwinHelpers.AppendHeader(Store, key, value); } /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. public void AppendValues(string key, params string[] values) { OwinHelpers.AppendHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. public void AppendCommaSeparatedValues(string key, params string[] values) { OwinHelpers.AppendHeaderJoined(Store, key, values); } /// /// Sets a specific header value. /// /// The header name. /// The header value. public void Set(string key, string value) { OwinHelpers.SetHeader(Store, key, value); } /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. public void SetValues(string key, params string[] values) { OwinHelpers.SetHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. public void SetCommaSeparatedValues(string key, params string[] values) { OwinHelpers.SetHeaderJoined(Store, key, values); } /// /// Adds the given header and values to the collection. /// /// The header name. /// The header values. public void Add(string key, string[] value) { Store.Add(key, value); } /// /// Determines whether the contains a specific key. /// /// The key. /// true if the contains a specific key; otherwise, false. public bool ContainsKey(string key) { return Store.ContainsKey(key); } /// /// Removes the given header from the collection. /// /// The header name. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(string key) { return Store.Remove(key); } /// /// Retrieves a value from the dictionary. /// /// The header name. /// The value. /// true if the contains the key; otherwise, false. public bool TryGetValue(string key, out string[] value) { return Store.TryGetValue(key, out value); } /// /// Adds a new list of items to the collection. /// /// The item to add. public void Add(KeyValuePair item) { Store.Add(item); } /// /// Clears the entire list of objects. /// public void Clear() { Store.Clear(); } /// /// Returns a value indicating whether the specified object occurs within this collection. /// /// The item. /// true if the specified object occurs within this collection; otherwise, false. public bool Contains(KeyValuePair item) { return Store.Contains(item); } /// /// Copies the elements to a one-dimensional Array instance at the specified index. /// /// The one-dimensional Array that is the destination of the specified objects copied from the . /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) { Store.CopyTo(array, arrayIndex); } /// /// Removes the given item from the the collection. /// /// The item. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(KeyValuePair item) { return Store.Remove(item); } } /// /// Represents the host portion of a Uri can be used to construct Uri's properly formatted and encoded for use in /// HTTP headers. /// partial struct HostString : IEquatable { private readonly string _value; /// /// Creates a new HostString without modification. The value should be Unicode rather than punycode, and may have a port. /// IPv4 and IPv6 addresses are also allowed, and also may have ports. /// /// public HostString(string value) { _value = value; } /// /// Returns the original value from the constructor. /// public string Value { get { return _value; } } /// /// Returns the value as normalized by ToUriComponent(). /// /// public override string ToString() { return ToUriComponent(); } /// /// Returns the value properly formatted and encoded for use in a URI in a HTTP header. /// Any Unicode is converted to punycode. IPv6 addresses will have brackets added if they are missing. /// /// public string ToUriComponent() { int index; if (string.IsNullOrEmpty(_value)) { return string.Empty; } else if (_value.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port return _value; } else if ((index = _value.IndexOf(':')) >= 0 && index < _value.Length - 1 && _value.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons return "[" + _value + "]"; } else if (index >= 0) { // Has a port string port = _value.Substring(index); var mapping = new IdnMapping(); return mapping.GetAscii(_value, 0, index) + port; } else { var mapping = new IdnMapping(); return mapping.GetAscii(_value); } } /// /// Creates a new HostString from the given uri component. /// Any punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(string uriComponent) { if (!string.IsNullOrEmpty(uriComponent)) { int index; if (uriComponent.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port } else if ((index = uriComponent.IndexOf(':')) >= 0 && index < uriComponent.Length - 1 && uriComponent.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons } else if (uriComponent.IndexOf("xn--", StringComparison.Ordinal) >= 0) { // Contains punycode if (index >= 0) { // Has a port string port = uriComponent.Substring(index); IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent, 0, index) + port; } else { IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent); } } } return new HostString(uriComponent); } /// /// Creates a new HostString from the host and port of the give Uri instance. /// Punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new HostString(uri.GetComponents( UriComponents.NormalizedHost | // Always convert punycode to Unicode. UriComponents.HostAndPort, UriFormat.Unescaped)); } /// /// Compares the equality of the Value property, ignoring case. /// /// /// public bool Equals(HostString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares against the given object only if it is a HostString. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HostString && Equals((HostString)obj); } /// /// Gets a hash code for the value. /// /// public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(HostString left, HostString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(HostString left, HostString right) { return !left.Equals(right); } } /// /// Contains the parsed form values. /// partial interface IFormCollection : IReadableStringCollection {} /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial interface IHeaderDictionary : IReadableStringCollection, IDictionary { /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. new string this[string key] { get; set; } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. IList GetCommaSeparatedValues(string key); /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. void Append(string key, string value); /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. void AppendValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. void AppendCommaSeparatedValues(string key, params string[] values); /// /// Sets a specific header value. /// /// The header name. /// The header value. void Set(string key, string value); /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. void SetValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. void SetCommaSeparatedValues(string key, params string[] values); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinContext { /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. IOwinRequest Request { get; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. IOwinResponse Response { get; } /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. TextWriter TraceOutput { get; set; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinContext Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinRequest { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or set the HTTP method. /// /// The HTTP method. string Method { get; set; } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. string Scheme { get; set; } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. bool IsSecure { get; } /// /// Gets or set the Host header. May include the port. /// /// The Host header. HostString Host { get; set; } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. PathString PathBase { get; set; } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. PathString Path { get; set; } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. QueryString QueryString { get; set; } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. IReadableStringCollection Query { get; } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. Uri Uri { get; } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. string Protocol { get; set; } /// /// Gets the request headers. /// /// The request headers. IHeaderDictionary Headers { get; } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. RequestCookieCollection Cookies { get; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. string CacheControl { get; set; } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. string MediaType { get; set; } /// /// Gets or set the Accept header. /// /// The Accept header. string Accept { get; set; } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. Stream Body { get; set; } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. CancellationToken CallCancelled { get; set; } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. string LocalIpAddress { get; set; } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. int? LocalPort { get; set; } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. string RemoteIpAddress { get; set; } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. int? RemotePort { get; set; } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. ClaimsPrincipal User { get; set; } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. Task ReadFormAsync(); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinRequest Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinResponse { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. int StatusCode { get; set; } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. string ReasonPhrase { get; set; } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. string Protocol { get; set; } /// /// Gets the response header collection. /// /// The response header collection. IHeaderDictionary Headers { get; } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. ResponseCookieCollection Cookies { get; } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. long? ContentLength { get; set; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Expires header. /// /// The Expires header. DateTimeOffset? Expires { get; set; } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. string ETag { get; set; } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. Stream Body { get; set; } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. void OnSendingHeaders(Action callback, object state); /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. void Redirect(string location); /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. void Write(string text); /// /// Writes the given bytes to the response body stream. /// /// The response data. void Write(byte[] data); /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. void Write(byte[] data, int offset, int count); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(string text); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(string text, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, int offset, int count, CancellationToken token); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinResponse Set(string key, T value); } /// /// Accessors for headers, query, forms, etc. /// partial interface IReadableStringCollection : IEnumerable> { /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string this[string key] { get; } // Joined /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string Get(string key); // Joined /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// IList GetValues(string key); // Raw } internal static class OwinConstants { #region OWIN v1.0.0 - 3.2.1. Request Data // http://owin.org/spec/owin-1.0.0.html public const string RequestScheme = "owin.RequestScheme"; public const string RequestMethod = "owin.RequestMethod"; public const string RequestPathBase = "owin.RequestPathBase"; public const string RequestPath = "owin.RequestPath"; public const string RequestQueryString = "owin.RequestQueryString"; public const string RequestProtocol = "owin.RequestProtocol"; public const string RequestHeaders = "owin.RequestHeaders"; public const string RequestBody = "owin.RequestBody"; public const string RequestUser = "owin.RequestUser"; //owin 1.0.1 #endregion #region OWIN v1.0.0 - 3.2.2. Response Data // http://owin.org/spec/owin-1.0.0.html public const string ResponseStatusCode = "owin.ResponseStatusCode"; public const string ResponseReasonPhrase = "owin.ResponseReasonPhrase"; public const string ResponseProtocol = "owin.ResponseProtocol"; public const string ResponseHeaders = "owin.ResponseHeaders"; public const string ResponseBody = "owin.ResponseBody"; #endregion #region OWIN v1.0.0 - 3.2.3. Other Data // http://owin.org/spec/owin-1.0.0.html public const string CallCancelled = "owin.CallCancelled"; public const string OwinVersion = "owin.Version"; #endregion #region OWIN Keys for IAppBuilder.Properties internal static class Builder { public const string AddSignatureConversion = "builder.AddSignatureConversion"; public const string DefaultApp = "builder.DefaultApp"; } #endregion #region OWIN Key Guidelines and Common Keys - 6. Common keys // http://owin.org/spec/CommonKeys.html internal static class CommonKeys { public const string ClientCertificate = "ssl.ClientCertificate"; public const string RemoteIpAddress = "server.RemoteIpAddress"; public const string RemotePort = "server.RemotePort"; public const string LocalIpAddress = "server.LocalIpAddress"; public const string LocalPort = "server.LocalPort"; public const string IsLocal = "server.IsLocal"; public const string TraceOutput = "host.TraceOutput"; public const string Addresses = "host.Addresses"; public const string AppName = "host.AppName"; public const string Capabilities = "server.Capabilities"; public const string OnSendingHeaders = "server.OnSendingHeaders"; public const string OnAppDisposing = "host.OnAppDisposing"; public const string Scheme = "scheme"; public const string Host = "host"; public const string Port = "port"; public const string Path = "path"; } #endregion #region SendFiles v0.3.0 // http://owin.org/extensions/owin-SendFile-Extension-v0.3.0.htm internal static class SendFiles { // 3.1. Startup public const string Version = "sendfile.Version"; public const string Support = "sendfile.Support"; public const string Concurrency = "sendfile.Concurrency"; // 3.2. Per Request public const string SendAsync = "sendfile.SendAsync"; } #endregion #region Opaque v0.3.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class OpaqueConstants { // 3.1. Startup public const string Version = "opaque.Version"; // 3.2. Per Request public const string Upgrade = "opaque.Upgrade"; // 5. Consumption public const string Stream = "opaque.Stream"; // public const string Version = "opaque.Version"; // redundant, declared above public const string CallCancelled = "opaque.CallCancelled"; } #endregion #region WebSocket v0.4.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class WebSocket { // 3.1. Startup public const string Version = "websocket.Version"; // 3.2. Per Request public const string Accept = "websocket.Accept"; // 4. Accept public const string SubProtocol = "websocket.SubProtocol"; // 5. Consumption public const string SendAsync = "websocket.SendAsync"; public const string ReceiveAsync = "websocket.ReceiveAsync"; public const string CloseAsync = "websocket.CloseAsync"; // public const string Version = "websocket.Version"; // redundant, declared above public const string CallCancelled = "websocket.CallCancelled"; public const string ClientCloseStatus = "websocket.ClientCloseStatus"; public const string ClientCloseDescription = "websocket.ClientCloseDescription"; } #endregion #region Security v0.1.0 // http://owin.org/extensions/owin-Security-Extension-v0.1.0.htm internal static class Security { // 3.2. Per Request public const string User = "server.User"; public const string Authenticate = "security.Authenticate"; // 3.3. Response public const string SignIn = "security.SignIn"; public const string SignOut = "security.SignOut"; public const string SignOutProperties = "security.SignOutProperties"; public const string Challenge = "security.Challenge"; } #endregion } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinContext : IOwinContext { /// /// Create a new context with only request and response header collections. /// public OwinContext() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Create a new wrapper. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinContext(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. public virtual IOwinRequest Request { get; private set; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. public virtual IOwinResponse Response { get; private set; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. public virtual TextWriter TraceOutput { get { return Get(OwinConstants.CommonKeys.TraceOutput); } set { Set(OwinConstants.CommonKeys.TraceOutput, value); } } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinContext Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinRequest : IOwinRequest { /// /// Create a new context with only request and response header collections. /// public OwinRequest() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Create a new environment wrapper exposing request properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinRequest(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or set the HTTP method. /// /// The HTTP method. public virtual string Method { get { return Get(OwinConstants.RequestMethod); } set { Set(OwinConstants.RequestMethod, value); } } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. public virtual string Scheme { get { return Get(OwinConstants.RequestScheme); } set { Set(OwinConstants.RequestScheme, value); } } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. public virtual bool IsSecure { get { return string.Equals(Scheme, Constants.Https, StringComparison.OrdinalIgnoreCase); } } /// /// Gets or set the Host header. May include the port. /// /// The Host header. public virtual HostString Host { get { return new HostString(OwinHelpers.GetHost(this)); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Host, value.Value); } } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. public virtual PathString PathBase { get { return new PathString(Get(OwinConstants.RequestPathBase)); } set { Set(OwinConstants.RequestPathBase, value.Value); } } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. public virtual PathString Path { get { return new PathString(Get(OwinConstants.RequestPath)); } set { Set(OwinConstants.RequestPath, value.Value); } } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. public virtual QueryString QueryString { get { return new QueryString(Get(OwinConstants.RequestQueryString)); } set { Set(OwinConstants.RequestQueryString, value.Value); } } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. public virtual IReadableStringCollection Query { get { return new ReadableStringCollection(OwinHelpers.GetQuery(this)); } } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. public virtual Uri Uri { get { return new Uri(Scheme + "//" + Host + PathBase + Path + QueryString); } } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. public virtual string Protocol { get { return Get(OwinConstants.RequestProtocol); } set { Set(OwinConstants.RequestProtocol, value); } } /// /// Gets the request headers. /// /// The request headers. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.RequestHeaders); } } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. public RequestCookieCollection Cookies { get { return new RequestCookieCollection(OwinHelpers.GetCookies(this)); } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. public virtual string CacheControl { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.CacheControl); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.CacheControl, value); } } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. public virtual string MediaType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.MediaType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.MediaType, value); } } /// /// Gets or set the Accept header. /// /// The Accept header. public virtual string Accept { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Accept); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Accept, value); } } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. public virtual Stream Body { get { return Get(OwinConstants.RequestBody); } set { Set(OwinConstants.RequestBody, value); } } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. public virtual CancellationToken CallCancelled { get { return Get(OwinConstants.CallCancelled); } set { Set(OwinConstants.CallCancelled, value); } } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. public virtual string LocalIpAddress { get { return Get(OwinConstants.CommonKeys.LocalIpAddress); } set { Set(OwinConstants.CommonKeys.LocalIpAddress, value); } } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. public virtual int? LocalPort { get { int value; if (int.TryParse(LocalPortString, out value)) { return value; } return null; } set { if (value.HasValue) { LocalPortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.LocalPort); } } } private string LocalPortString { get { return Get(OwinConstants.CommonKeys.LocalPort); } set { Set(OwinConstants.CommonKeys.LocalPort, value); } } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. public virtual string RemoteIpAddress { get { return Get(OwinConstants.CommonKeys.RemoteIpAddress); } set { Set(OwinConstants.CommonKeys.RemoteIpAddress, value); } } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. public virtual int? RemotePort { get { int value; if (int.TryParse(RemotePortString, out value)) { return value; } return null; } set { if (value.HasValue) { RemotePortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.RemotePort); } } } private string RemotePortString { get { return Get(OwinConstants.CommonKeys.RemotePort); } set { Set(OwinConstants.CommonKeys.RemotePort, value); } } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. public virtual ClaimsPrincipal User { get { var claimsPrincipal = Get(OwinConstants.RequestUser); return claimsPrincipal ?? Get(OwinConstants.Security.User) as ClaimsPrincipal; } set { Set(OwinConstants.RequestUser, value); } } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. public async Task ReadFormAsync() { var form = Get("Microsoft.Owin.Form#collection"); if (form == null) { string text; // Don't close, it prevents re-winding. using (var reader = new StreamReader(Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 4 * 1024, leaveOpen: true)) { text = await reader.ReadToEndAsync(); } form = OwinHelpers.GetForm(text); Set("Microsoft.Owin.Form#collection", form); } return form; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinRequest Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinResponse : IOwinResponse { /// /// Create a new context with only request and response header collections. /// public OwinResponse() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Creates a new environment wrapper exposing response properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinResponse(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. public virtual int StatusCode { get { return Get(OwinConstants.ResponseStatusCode, 200); } set { Set(OwinConstants.ResponseStatusCode, value); } } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. public virtual string ReasonPhrase { get { return Get(OwinConstants.ResponseReasonPhrase); } set { Set(OwinConstants.ResponseReasonPhrase, value); } } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. public virtual string Protocol { get { return Get(OwinConstants.ResponseProtocol); } set { Set(OwinConstants.ResponseProtocol, value); } } /// /// Gets the response header collection. /// /// The response header collection. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.ResponseHeaders); } } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. public virtual ResponseCookieCollection Cookies { get { return new ResponseCookieCollection(Headers); } } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. public virtual long? ContentLength { get { long value; if (long.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentLength), out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentLength, value.Value.ToString(CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.ContentLength); } } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Expires header. /// /// The Expires header. public virtual DateTimeOffset? Expires { get { DateTimeOffset value; if (DateTimeOffset.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Expires), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Expires, value.Value.ToString(Constants.HttpDateFormat, CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.Expires); } } } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. public virtual string ETag { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ETag); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ETag, value); } } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. public virtual Stream Body { get { return Get(OwinConstants.ResponseBody); } set { Set(OwinConstants.ResponseBody, value); } } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. public virtual void OnSendingHeaders(Action callback, object state) { var onSendingHeaders = Get, object>>(OwinConstants.CommonKeys.OnSendingHeaders); if (onSendingHeaders == null) { throw new NotSupportedException(Resources.Exception_MissingOnSendingHeaders); } onSendingHeaders(callback, state); } /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. public virtual void Redirect(string location) { StatusCode = 302; OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Location, location); } /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. public virtual void Write(string text) { Write(Encoding.UTF8.GetBytes(text)); } /// /// Writes the given bytes to the response body stream. /// /// The response data. public virtual void Write(byte[] data) { Write(data, 0, data == null ? 0 : data.Length); } /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. public virtual void Write(byte[] data, int offset, int count) { Body.Write(data, offset, count); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text) { return WriteAsync(text, CancellationToken.None); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text, CancellationToken token) { return WriteAsync(Encoding.UTF8.GetBytes(text), token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data) { return WriteAsync(data, CancellationToken.None); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, CancellationToken token) { return WriteAsync(data, 0, data == null ? 0 : data.Length, token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, int offset, int count, CancellationToken token) { return Body.WriteAsync(data, offset, count, token); } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { return Get(key, default(T)); } private T Get(string key, T fallback) { object value; return Environment.TryGetValue(key, out value) ? (T)value : fallback; } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinResponse Set(string key, T value) { Environment[key] = value; return this; } } /// /// Provides correct escaping for Path and PathBase values when needed to reconstruct a request or redirect URI string /// partial struct PathString : IEquatable { private static readonly Func EscapeDataString = Uri.EscapeDataString; /// /// Represents the empty path. This field is read-only. /// public static readonly PathString Empty = new PathString(String.Empty); private readonly string _value; /// /// Initialize the path string with a given value. This value must be in un-escaped format. Use /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format. /// /// The unescaped path to be assigned to the Value property. public PathString(string value) { if (!String.IsNullOrEmpty(value) && value[0] != '/') { throw new ArgumentException(Resources.Exception_PathMustStartWithSlash, "value"); } _value = value; } /// /// The unescaped path value /// public string Value { get { return _value; } } /// /// True if the path is not empty /// public bool HasValue { get { return !String.IsNullOrEmpty(_value); } } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public override string ToString() { return ToUriComponent(); } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public string ToUriComponent() { if (HasValue) { if (RequiresEscaping(_value)) { // TODO: Measure the cost of this escaping and consider optimizing. return String.Join("/", _value.Split('/').Select(EscapeDataString)); } return _value; } return String.Empty; } // Very conservative, these characters do not need to be escaped in a path. private static bool RequiresEscaping(string value) { for (int i = 0; i < value.Length; i++) { char c = value[i]; // Check conservatively for safe characters. See http://www.ietf.org/rfc/rfc3986.txt bool safeChar = (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '/' || c == '-' || c == '_'); if (!safeChar) { return true; } } return false; } /// /// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a path. /// /// The escaped path as it appears in the URI format. /// The resulting PathString public static PathString FromUriComponent(string uriComponent) { // REVIEW: what is the exactly correct thing to do? return new PathString(Uri.UnescapeDataString(uriComponent)); } /// /// Returns an PathString given the path as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting PathString public static PathString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } // REVIEW: what is the exactly correct thing to do? return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)); } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// public bool StartsWithSegments(PathString other) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { return value1.Length == value2.Length || value1[value2.Length] == '/'; } return false; } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// Any remaining segments from this instance not included in the other instance. /// public bool StartsWithSegments(PathString other, out PathString remaining) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { if (value1.Length == value2.Length || value1[value2.Length] == '/') { remaining = new PathString(value1.Substring(value2.Length)); return true; } } remaining = Empty; return false; } /// /// Adds two PathString instances into a combined PathString value. /// /// The combined PathString value public PathString Add(PathString other) { return new PathString(Value + other.Value); } /// /// Combines a PathString and QueryString into the joined URI formatted string value. /// /// The joined URI formatted string value public string Add(QueryString other) { return ToUriComponent() + other.ToUriComponent(); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public bool Equals(PathString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares this PathString value to another value using a specific StringComparison type /// /// The second PathString for comparison /// The StringComparison type to use /// True if both PathString values are equal public bool Equals(PathString other, StringComparison comparisonType) { return string.Equals(_value, other._value, comparisonType); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is PathString && Equals((PathString)obj, StringComparison.OrdinalIgnoreCase); } /// /// Returns the hash code for the PathString value. The hash code is provided by the OrdinalIgnoreCase implementation. /// /// The hash code public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are equal public static bool operator ==(PathString left, PathString right) { return left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are not equal public static bool operator !=(PathString left, PathString right) { return !left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static PathString operator +(PathString left, PathString right) { return left.Add(right); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static string operator +(PathString left, QueryString right) { return left.Add(right); } } /// /// Provides correct handling for QueryString value when needed to reconstruct a request or redirect URI string /// partial struct QueryString : IEquatable { /// /// Represents the empty query string. This field is read-only. /// public static readonly QueryString Empty = new QueryString(String.Empty); private readonly string _value; /// /// Initalize the query string with a given value. This value must be in escaped and delimited format without /// a leading '?' character. /// /// The query string to be assigned to the Value property. public QueryString(string value) { _value = value; } /// /// Initialize a query string with a single given parameter name and value. The value is /// /// The unencoded parameter name /// The unencoded parameter value public QueryString(string name, string value) { _value = Uri.EscapeDataString(name) + '=' + Uri.EscapeDataString(value); } /// /// The unescaped query string without the leading '?' character /// public string Value { get { return _value; } } /// /// True if the query string is not empty /// public bool HasValue { get { return !String.IsNullOrWhiteSpace(_value); } } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentally /// dangerous are escaped. /// /// The query string value public override string ToString() { return ToUriComponent(); } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentially /// dangerous are escaped. /// /// The query string value public string ToUriComponent() { // Escape things properly so System.Uri doesn't mis-interpret the data. return HasValue ? "?" + _value.Replace("#", "%23") : String.Empty; } /// /// Returns an QueryString given the query as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a query. /// /// The escaped query as it appears in the URI format. /// The resulting QueryString public static QueryString FromUriComponent(string uriComponent) { if (String.IsNullOrEmpty(uriComponent)) { return new QueryString(string.Empty); } if (uriComponent[0] != '?') { throw new ArgumentException(Resources.Exception_QueryStringMustStartWithDelimiter, "uriComponent"); } return new QueryString(uriComponent.Substring(1)); } /// /// Returns an QueryString given the query as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting QueryString public static QueryString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new QueryString(uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped)); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public bool Equals(QueryString other) { return string.Equals(_value, other._value); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is QueryString && Equals((QueryString)obj); } /// /// Returns the hash code for this instance. /// /// public override int GetHashCode() { return (_value != null ? _value.GetHashCode() : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(QueryString left, QueryString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(QueryString left, QueryString right) { return !left.Equals(right); } } /// /// Accessors for query, forms, etc. /// partial class ReadableStringCollection : IReadableStringCollection { /// /// Create a new wrapper /// /// public ReadableStringCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string this[string key] { get { return Get(key); } } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string Get(string key) { return OwinHelpers.GetJoinedValue(Store, key); } /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// public IList GetValues(string key) { string[] values; Store.TryGetValue(key, out values); return values; } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } /// /// A wrapper for the request Cookie header /// partial class RequestCookieCollection : IEnumerable> { /// /// Create a new wrapper /// /// public RequestCookieCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Returns null rather than throwing KeyNotFoundException /// /// /// public string this[string key] { get { string value; Store.TryGetValue(key, out value); return value; } } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } internal static class Resources { internal const string Exception_MissingOnSendingHeaders = "The OWIN key 'server.OnSsoendingHeaders' is not available for this request."; internal const string Exception_PathMustStartWithSlash = "The path must start with a '/' followed by one or more characters."; internal const string Exception_QueryStringMustStartWithDelimiter = "The query string must start with a '?' unless null or empty."; } /// /// A wrapper for the response Set-Cookie header /// partial class ResponseCookieCollection { /// /// Create a new wrapper /// /// public ResponseCookieCollection(IHeaderDictionary headers) { if (headers == null) { throw new ArgumentNullException("headers"); } Headers = headers; } private IHeaderDictionary Headers { get; set; } /// /// Add a new cookie and value /// /// /// public void Append(string key, string value) { Headers.AppendValues(Constants.Headers.SetCookie, Uri.EscapeDataString(key) + "=" + Uri.EscapeDataString(value) + "; path=/"); } /// /// Add a new cookie /// /// /// /// public void Append(string key, string value, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); bool expiresHasValue = options.Expires.HasValue; string setCookieValue = string.Concat( Uri.EscapeDataString(key), "=", Uri.EscapeDataString(value ?? string.Empty), !domainHasValue ? null : "; domain=", !domainHasValue ? null : options.Domain, !pathHasValue ? null : "; path=", !pathHasValue ? null : options.Path, !expiresHasValue ? null : "; expires=", !expiresHasValue ? null : options.Expires.Value.ToString("ddd, dd-MMM-yyyy HH:mm:ss ", CultureInfo.InvariantCulture) + "GMT", !options.Secure ? null : "; secure", !options.HttpOnly ? null : "; HttpOnly"); Headers.AppendValues("Set-Cookie", setCookieValue); } /// /// Sets an expired cookie /// /// public void Delete(string key) { Func predicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); var deleteCookies = new[] { Uri.EscapeDataString(key) + "=; expires=Thu, 01-Jan-1970 00:00:00 GMT" }; IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues == null || existingValues.Count == 0) { Headers.SetValues(Constants.Headers.SetCookie, deleteCookies); } else { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !predicate(value)).Concat(deleteCookies).ToArray()); } } /// /// Sets an expired cookie /// /// /// public void Delete(string key, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); Func rejectPredicate; if (domainHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("domain=" + options.Domain, StringComparison.OrdinalIgnoreCase) != -1; } else if (pathHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("path=" + options.Path, StringComparison.OrdinalIgnoreCase) != -1; } else { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); } IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues != null) { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !rejectPredicate(value)).ToArray()); } Append(key, string.Empty, new CookieOptions { Path = options.Path, Domain = options.Domain, Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), }); } } static partial class IOwinResponseExtension { /// /// Registers for an event that fires when the response headers are sent. /// /// The owin response /// The callback method. /// The callback state. public static void OnSendingHeaders(this IOwinResponse response, Action callback, T state) { if (response == null) { throw new ArgumentNullException("response"); } Action innerCallback = innerState => callback((T)innerState); response.OnSendingHeaders(innerCallback, state); } } } ================================================ FILE: src/chapter08/OwinMiddlewareTests/SampleTest.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; using LibOwin; namespace OwinMiddlewareTests { using AppFunc = Func, Task>; // see example explanation on xUnit.net website: // https://xunit.github.io/docs/getting-started-dnx.html public class SampleTest { private AppFunc noOp = env => Task.FromResult(0); private Func MW = next => async env => { var ctx = new OwinContext(env); if (ctx.Request.Path.Value == "/test/path") ctx.Response.StatusCode = 404; else await next(env); }; private Func ConsoleMV = next => env => { var context = new OwinContext(env); var method = context.Request.Method; var path = context.Request.Path; System.Console.WriteLine($"Got request: {method} {path}"); return next(env); }; [Fact] public void CreateInvokeLambdaMw() { var ctx = new OwinContext(); ctx.Request.Scheme = LibOwin.Infrastructure.Constants.Https; ctx.Request.Path = new PathString("/test/path"); ctx.Request.Method = "GET"; var pipeline = ConsoleMV(MW(noOp)); var env = ctx.Environment; pipeline(env); Assert.Equal(404, ctx.Response.StatusCode); } int Add(int x, int y) { return x + y; } } public class ConsoleMiddleware { private AppFunc next; public ConsoleMiddleware(AppFunc next) { this.next = next; } public Task Invoke(IDictionary env) { var context = new OwinContext(env); var method = context.Request.Method; var path = context.Request.Path; System.Console.WriteLine($"Got request: {method} {path}"); return next(env); } } } ================================================ FILE: src/chapter08/OwinMiddlewareTests/project.json ================================================ { "version": "1.0.0-*", "buildOptions": { "preserveCompilationContext": true }, "dependencies": { "dotnet-test-xunit": "2.2.0-preview2-build1029", "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "xunit": "2.1.0", "System.Security.Claims": "4.0.1", "System.Globalization": "4.0.11" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ], "dependencies": { } } }, "testRunner": "xunit", "tooling": { "defaultNamespace": "OwinMiddlewareTests" } } ================================================ FILE: src/chapter10/.idea.ch10/riderModule.iml ================================================ ================================================ FILE: src/chapter10/ApiGatewayMock/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/chapter10/ApiGatewayMock/ApiGatewayMock.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) fe109a02-bf1b-46fc-87c8-2a68781ce8a4 ApiGatewayMock .\obj .\bin\ 2.0 ================================================ FILE: src/chapter10/ApiGatewayMock/LoyalProgramClient.cs ================================================ using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using Polly; namespace ApiGatewayMock { public class LoyaltyProgramClient { private static Policy exponentialRetryPolicy = Policy .Handle() .WaitAndRetryAsync( 3, attempt => TimeSpan.FromMilliseconds(100*Math.Pow(2, attempt)), (_, __) => Console.WriteLine("retrying..." + _) ); private static Policy circuitBreaker = Policy .Handle() .CircuitBreaker(5, TimeSpan.FromMinutes(5)); private string hostName; private readonly IHttpClientFactory httpClientFactory; public LoyaltyProgramClient( string loyalProgramMicroserviceHostName, IHttpClientFactory httpClientFactory) { this.hostName = loyalProgramMicroserviceHostName; this.httpClientFactory = httpClientFactory; } public async Task QueryUser(int userId) { return await circuitBreaker.ExecuteAsync(() => DoUserQuery(userId)).ConfigureAwait(false); } private async Task DoUserQuery(int userId) { var userResource = $"/users/{userId}"; using (var httpClient = await this.httpClientFactory.Create(new Uri($"http://{this.hostName}"), "loyalty_program_write").ConfigureAwait(false)) { var response = await httpClient.GetAsync(userResource).ConfigureAwait(false); ThrowOnTransientFailure(response); return response; } } private static void ThrowOnTransientFailure(HttpResponseMessage response) { if (((int) response.StatusCode) < 200 || ((int) response.StatusCode) > 499) throw new Exception(response.StatusCode.ToString()); } public async Task RegisterUser(LoyaltyProgramUser newUser) { return await exponentialRetryPolicy.ExecuteAsync(() => DoRegisterUser(newUser)).ConfigureAwait(false); } private async Task DoRegisterUser(LoyaltyProgramUser newUser) { using (var httpClient = await this.httpClientFactory.Create(new Uri($"http://{this.hostName}"), "loyalty_program_write").ConfigureAwait(false)) { var response = await httpClient.PostAsync("/users/", new StringContent(JsonConvert.SerializeObject(newUser), Encoding.UTF8, "application/json")).ConfigureAwait(false); ThrowOnTransientFailure(response); return response; } } public async Task UpdateUser(LoyaltyProgramUser user) { return await exponentialRetryPolicy.ExecuteAsync(() => DoUpdateUser(user)).ConfigureAwait(false); } private async Task DoUpdateUser(LoyaltyProgramUser user) { using (var httpClient = await this.httpClientFactory.Create(new Uri($"http://{this.hostName}"), "loyalty_program_write").ConfigureAwait(false)) { httpClient.BaseAddress = new Uri($"http://{this.hostName}"); var response = await httpClient.PutAsync($"/users/{user.Id}", new StringContent(JsonConvert.SerializeObject(user), Encoding.UTF8, "application/json")).ConfigureAwait(false); ThrowOnTransientFailure(response); return response; } } } public class LoyaltyProgramUser { public int Id { get; set; } public string Name { get; set; } public int LoyaltyPoints { get; set; } public LoyaltyProgramSettings Settings { get; set; } } public class LoyaltyProgramSettings { public string[] Interests { get; set; } } } ================================================ FILE: src/chapter10/ApiGatewayMock/Program.cs ================================================ using static System.Console; namespace ApiGatewayMock { using System; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using IdentityModel.Client; using Newtonsoft.Json; public interface IHttpClientFactory { Task Create(Uri uri, string scope); } public class HttpClientFactory : IHttpClientFactory { private readonly TokenClient tokenClient; public HttpClientFactory() { this.tokenClient = new TokenClient( "http://localhost:5001/connect/token", "api_gateway", "secret"); } public async Task Create(Uri uri, string scope) { var response = await this.tokenClient.RequestClientCredentialsAsync(scope).ConfigureAwait(false); var client = new HttpClient() {BaseAddress = uri}; client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", response.AccessToken); return client; } } public class Program { private LoyaltyProgramClient client; public static void Main(string[] arg) => new Program().Main(); public void Main() { this.client = new LoyaltyProgramClient("localhost:5000", new HttpClientFactory()); WriteLine("Welcome to the API Gateway Mock."); var cont = true; while (cont) { WriteLine(); WriteLine(); WriteLine("********************"); WriteLine("Choose one of:"); WriteLine("q - to query the Loyalty Program Microservice for a user with id ."); WriteLine("r - to register a user with id with the Loyalty Program Microservice."); WriteLine("u - to update a user with new comman separated interests"); WriteLine("exit - to exit"); WriteLine("********************"); var cmd = ReadLine(); cont = ProcessCommand(cmd); } } private bool ProcessCommand(string cmd) { if ("exit".Equals(cmd)) return false; if (cmd.StartsWith("q")) ProcessUserQuery(cmd); else if (cmd.StartsWith("r")) ProcessUserRegistration(cmd); else if (cmd.StartsWith("u")) ProcessUpdateUser(cmd); else WriteLine("Did not understand command :("); return true; } private void ProcessUserQuery(string cmd) { int userId; if (!int.TryParse(cmd.Substring(1), out userId)) WriteLine("Please specify user id as an int"); else { var response = this.client.QueryUser(userId).Result; PrettyPrintResponse(response); } } private void ProcessUserRegistration(string cmd) { var newUser = new LoyaltyProgramUser {Name = cmd.Substring(1).Trim()}; var response = this.client.RegisterUser(newUser).Result; PrettyPrintResponse(response); } private static async void PrettyPrintResponse(HttpResponseMessage response) { WriteLine("Status code: " + (response?.StatusCode.ToString() ?? "command failed")); WriteLine("Headers: " + (response?.Headers.Aggregate("", (acc, h) => acc + "\n\t" + h.Key + ": " + h.Value) ?? "")); WriteLine("Body: " + await (response?.Content.ReadAsStringAsync() ?? Task.FromResult("")).ConfigureAwait(false)); } private async void ProcessUpdateUser(string cmd) { int userId; if (!int.TryParse(cmd.Split(' ').Skip(1).First(), out userId)) WriteLine("Plaese speciffy user id as an int"); else { var response = this.client.QueryUser(userId).Result; if (response.StatusCode == HttpStatusCode.OK) { var user = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync().ConfigureAwait(false)); var newInterests = cmd.Substring(cmd.IndexOf(' ', 2)).Split(',').Select(i => i.Trim()); user.Settings = new LoyaltyProgramSettings { Interests = user.Settings?.Interests.Union(newInterests).ToArray() ?? newInterests.ToArray() }; PrettyPrintResponse(this.client.UpdateUser(user).Result); } } } } } ================================================ FILE: src/chapter10/ApiGatewayMock/project.json ================================================ { "version": "1.0.0-*", "buildOptions": { "emitEntryPoint": true }, "dependencies": { "Microsoft.NETCore.App": { "type": "platform", "version": "1.0.0" }, "Newtonsoft.Json": "8.0.4-beta1", "Polly": "4.2.1", "IdentityModel": "2.0.0-beta3", "System.Net.Http": "4.1.0" }, "frameworks": { "netcoreapp1.0": { "imports": "dnxcore50" } }, "tooling": { "defaultNamespace": "ApiGatewayMock" } } ================================================ FILE: src/chapter10/Login/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/chapter10/Login/Configuration/Clients.cs ================================================ namespace Login.Configuration { using System.Collections.Generic; using IdentityServer4.Models; public class Clients { public static IEnumerable Get() => new List { new Client { ClientName = "API Gateway", ClientId = "api_gateway", ClientSecrets = new List { new Secret("secret".Sha256()) }, AllowedScopes = new List { "loyalty_program_write", }, AllowedGrantTypes = GrantTypes.ClientCredentials }, new Client { ClientName = "Web Client", ClientId = "web", RedirectUris = new List { "http://localhost:5003/signin-oidc", }, PostLogoutRedirectUris = new List { "http://localhost:5003/", }, AllowedScopes = new List { "openid", "email", "profile", } } }; } } ================================================ FILE: src/chapter10/Login/Configuration/Scopes.cs ================================================ namespace Login.Configuration { using System.Collections.Generic; using IdentityServer4.Models; public class Scopes { public static IEnumerable Get() => new[] { // standard OpenID Connect scopes StandardScopes.OpenId, StandardScopes.ProfileAlwaysInclude, StandardScopes.EmailAlwaysInclude, new Scope { Name = "loyalty_program_write", DisplayName = "Loyalty Program write access", Type = ScopeType.Resource, } }; } } ================================================ FILE: src/chapter10/Login/Configuration/Users.cs ================================================ namespace Login.Configuration { using System.Collections.Generic; using System.Security.Claims; using IdentityModel; using IdentityServer4.Services.InMemory; static class Users { public static List Get() => new List { new InMemoryUser{Subject = "818727", Username = "alice", Password = "alice", Claims = new[] { new Claim(JwtClaimTypes.Name, "Alice Smith"), new Claim(JwtClaimTypes.GivenName, "Alice"), new Claim(JwtClaimTypes.FamilyName, "Smith"), new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.Role, "User"), new Claim(JwtClaimTypes.Id, "1", ClaimValueTypes.Integer64) } }, new InMemoryUser{Subject = "88421113", Username = "bob", Password = "bob", Claims = new[] { new Claim(JwtClaimTypes.Name, "Bob Smith"), new Claim(JwtClaimTypes.GivenName, "Bob"), new Claim(JwtClaimTypes.FamilyName, "Smith"), new Claim(JwtClaimTypes.Email, "BobSmith@email.com"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.Role, "User"), new Claim(JwtClaimTypes.Id, "2", ClaimValueTypes.Integer64) } } }; } } ================================================ FILE: src/chapter10/Login/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/chapter10/Login/Login.xproj ================================================  14.0.25123 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) f416d0b1-71ee-400e-91e2-fc2b047da208 Login .\obj .\bin\ 2.0 ================================================ FILE: src/chapter10/Login/Program.cs ================================================ namespace Login { using System.IO; using Microsoft.AspNetCore.Hosting; public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .UseUrls("http://localhost:5001") .Build(); host.Run(); } } } ================================================ FILE: src/chapter10/Login/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "IdentityServer": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/chapter10/Login/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/chapter10/Login/Startup.cs ================================================ namespace Login { using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Configuration; public class Startup { private readonly IHostingEnvironment environment; public Startup(IHostingEnvironment env) { this.environment = env; } public void ConfigureServices(IServiceCollection services) { var cert = new X509Certificate2(Path.Combine(this.environment.ContentRootPath, "idsrv3test.pfx"), "idsrv3test"); services.AddSingleton(); var builder = services.AddIdentityServer().SetSigningCredential(cert); builder.AddInMemoryClients(Clients.Get()); builder.AddInMemoryScopes(Scopes.Get()); builder.AddInMemoryUsers(Users.Get()); services.AddMvc(); } public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(LogLevel.Trace); loggerFactory.AddDebug(LogLevel.Trace); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationScheme = "Temp", AutomaticAuthenticate = false, AutomaticChallenge = false }); app.UseIdentityServer(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); } } } ================================================ FILE: src/chapter10/Login/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.0.0", "IdentityServer4": "1.0.0-beta5", "SeriLog": "2.0.0-rc-600" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "IdentityServer" } } ================================================ FILE: src/chapter10/Login/web.config ================================================ ================================================ FILE: src/chapter10/LoyaltyProgram/.gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Microsoft Azure ApplicationInsights config file ApplicationInsights.config # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ ================================================ FILE: src/chapter10/LoyaltyProgram/Application_Packages/LibOwin.cs ================================================ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. // Copyright (c) Damian Hickey. All rights reserved. See License.txt in the project root for license information. // https://github.com/damianh/LibOwin // Modifying this file may result in difficulties when upgrading the package. // All types are internal. Add a LIBOWIN_PUBLIC compilation symbol to make them public. namespace LibOwin.Infrastructure { using System; using System.Collections; using System.Collections.Generic; using System.Linq; internal static partial class Constants { internal const string Https = "HTTPS"; internal const string HttpDateFormat = "r"; internal static partial class Headers { internal const string ContentType = "Content-Type"; internal const string CacheControl = "Cache-Control"; internal const string MediaType = "Media-Type"; internal const string Accept = "Accept"; internal const string Host = "Host"; internal const string ETag = "ETag"; internal const string Location = "Location"; internal const string ContentLength = "Content-Length"; internal const string SetCookie = "Set-Cookie"; internal const string Expires = "Expires"; } } internal struct HeaderSegment : IEquatable { private readonly StringSegment _formatting; private readonly StringSegment _data; // // Initializes a new instance of the class. // public HeaderSegment(StringSegment formatting, StringSegment data) { _formatting = formatting; _data = data; } public StringSegment Formatting { get { return _formatting; } } public StringSegment Data { get { return _data; } } #region Equality members public bool Equals(HeaderSegment other) { return _formatting.Equals(other._formatting) && _data.Equals(other._data); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegment && Equals((HeaderSegment)obj); } public override int GetHashCode() { unchecked { return (_formatting.GetHashCode() * 397) ^ _data.GetHashCode(); } } public static bool operator ==(HeaderSegment left, HeaderSegment right) { return left.Equals(right); } public static bool operator !=(HeaderSegment left, HeaderSegment right) { return !left.Equals(right); } #endregion } internal struct HeaderSegmentCollection : IEnumerable, IEquatable { private readonly string[] _headers; public HeaderSegmentCollection(string[] headers) { _headers = headers; } #region Equality members public bool Equals(HeaderSegmentCollection other) { return Equals(_headers, other._headers); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HeaderSegmentCollection && Equals((HeaderSegmentCollection)obj); } public override int GetHashCode() { return (_headers != null ? _headers.GetHashCode() : 0); } public static bool operator ==(HeaderSegmentCollection left, HeaderSegmentCollection right) { return left.Equals(right); } public static bool operator !=(HeaderSegmentCollection left, HeaderSegmentCollection right) { return !left.Equals(right); } #endregion public Enumerator GetEnumerator() { return new Enumerator(_headers); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } internal struct Enumerator : IEnumerator { private readonly string[] _headers; private int _index; private string _header; private int _headerLength; private int _offset; private int _leadingStart; private int _leadingEnd; private int _valueStart; private int _valueEnd; private int _trailingStart; private Mode _mode; private static readonly string[] NoHeaders = new string[0]; public Enumerator(string[] headers) { _headers = headers ?? NoHeaders; _header = string.Empty; _headerLength = -1; _index = -1; _offset = -1; _leadingStart = -1; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; _mode = Mode.Leading; } private enum Mode { Leading, Value, ValueQuoted, Trailing, Produce, } private enum Attr { Value, Quote, Delimiter, Whitespace } public HeaderSegment Current { get { return new HeaderSegment( new StringSegment(_header, _leadingStart, _leadingEnd - _leadingStart), new StringSegment(_header, _valueStart, _valueEnd - _valueStart)); } } object IEnumerator.Current { get { return Current; } } public void Dispose() { } public bool MoveNext() { while (true) { if (_mode == Mode.Produce) { _leadingStart = _trailingStart; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; if (_offset == _headerLength && _leadingStart != -1 && _leadingStart != _offset) { // Also produce trailing whitespace _leadingEnd = _offset; return true; } _mode = Mode.Leading; } // if end of a string if (_offset == _headerLength) { ++_index; _offset = -1; _leadingStart = 0; _leadingEnd = -1; _valueStart = -1; _valueEnd = -1; _trailingStart = -1; // if that was the last string if (_index == _headers.Length) { // no more move nexts return false; } // grab the next string _header = _headers[_index] ?? string.Empty; _headerLength = _header.Length; } while (true) { ++_offset; char ch = _offset == _headerLength ? (char)0 : _header[_offset]; // todo - array of attrs Attr attr = char.IsWhiteSpace(ch) ? Attr.Whitespace : ch == '\"' ? Attr.Quote : (ch == ',' || ch == (char)0) ? Attr.Delimiter : Attr.Value; switch (_mode) { case Mode.Leading: switch (attr) { case Attr.Delimiter: _leadingEnd = _offset; _mode = Mode.Produce; break; case Attr.Quote: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.ValueQuoted; break; case Attr.Value: _leadingEnd = _offset; _valueStart = _offset; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; case Mode.Value: switch (attr) { case Attr.Quote: _mode = Mode.ValueQuoted; break; case Attr.Delimiter: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; break; case Attr.Value: // more break; case Attr.Whitespace: _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Trailing; break; } break; case Mode.ValueQuoted: switch (attr) { case Attr.Quote: _mode = Mode.Value; break; case Attr.Delimiter: if (ch == (char)0) { _valueEnd = _offset; _trailingStart = _offset; _mode = Mode.Produce; } break; case Attr.Value: case Attr.Whitespace: // more break; } break; case Mode.Trailing: switch (attr) { case Attr.Delimiter: _mode = Mode.Produce; break; case Attr.Quote: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.ValueQuoted; break; case Attr.Value: // back into value _trailingStart = -1; _valueEnd = -1; _mode = Mode.Value; break; case Attr.Whitespace: // more break; } break; } if (_mode == Mode.Produce) { return true; } } } } public void Reset() { _index = 0; _offset = 0; _leadingStart = 0; _leadingEnd = 0; _valueStart = 0; _valueEnd = 0; } } } internal struct StringSegment : IEquatable { private readonly string _buffer; private readonly int _offset; private readonly int _count; // // Initializes a new instance of the class. // public StringSegment(string buffer, int offset, int count) { _buffer = buffer; _offset = offset; _count = count; } public string Buffer { get { return _buffer; } } public int Offset { get { return _offset; } } public int Count { get { return _count; } } public string Value { get { return _offset == -1 ? null : _buffer.Substring(_offset, _count); } } public bool HasValue { get { return _offset != -1 && _count != 0 && _buffer != null; } } #region Equality members public bool Equals(StringSegment other) { return string.Equals(_buffer, other._buffer) && _offset == other._offset && _count == other._count; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is StringSegment && Equals((StringSegment)obj); } public override int GetHashCode() { unchecked { int hashCode = (_buffer != null ? _buffer.GetHashCode() : 0); hashCode = (hashCode * 397) ^ _offset; hashCode = (hashCode * 397) ^ _count; return hashCode; } } public static bool operator ==(StringSegment left, StringSegment right) { return left.Equals(right); } public static bool operator !=(StringSegment left, StringSegment right) { return !left.Equals(right); } #endregion public bool StartsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public bool EndsWith(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count < textLength) { return false; } return string.Compare(_buffer, _offset + _count - textLength, text, 0, textLength, comparisonType) == 0; } public bool Equals(string text, StringComparison comparisonType) { if (text == null) { throw new ArgumentNullException("text"); } int textLength = text.Length; if (!HasValue || _count != textLength) { return false; } return string.Compare(_buffer, _offset, text, 0, textLength, comparisonType) == 0; } public string Substring(int offset, int length) { return _buffer.Substring(_offset + offset, length); } public StringSegment Subsegment(int offset, int length) { return new StringSegment(_buffer, _offset + offset, length); } public override string ToString() { return Value ?? string.Empty; } } internal static partial class OwinHelpers { private static readonly Action AddCookieCallback = (name, value, state) => { var dictionary = (IDictionary)state; if (!dictionary.ContainsKey(name)) { dictionary.Add(name, value); } }; private static readonly char[] SemicolonAndComma = new[] { ';', ',' }; internal static IDictionary GetCookies(IOwinRequest request) { var cookies = request.Get>("Microsoft.Owin.Cookies#dictionary"); if (cookies == null) { cookies = new Dictionary(StringComparer.Ordinal); request.Set("Microsoft.Owin.Cookies#dictionary", cookies); } string text = GetHeader(request.Headers, "Cookie"); if (request.Get("Microsoft.Owin.Cookies#text") != text) { cookies.Clear(); ParseDelimited(text, SemicolonAndComma, AddCookieCallback, cookies); request.Set("Microsoft.Owin.Cookies#text", text); } return cookies; } internal static void ParseDelimited(string text, char[] delimiters, Action callback, object state) { int textLength = text.Length; int equalIndex = text.IndexOf('='); if (equalIndex == -1) { equalIndex = textLength; } int scanIndex = 0; while (scanIndex < textLength) { int delimiterIndex = text.IndexOfAny(delimiters, scanIndex); if (delimiterIndex == -1) { delimiterIndex = textLength; } if (equalIndex < delimiterIndex) { while (scanIndex != equalIndex && char.IsWhiteSpace(text[scanIndex])) { ++scanIndex; } string name = text.Substring(scanIndex, equalIndex - scanIndex); string value = text.Substring(equalIndex + 1, delimiterIndex - equalIndex - 1); callback( Uri.UnescapeDataString(name.Replace('+', ' ')), Uri.UnescapeDataString(value.Replace('+', ' ')), state); equalIndex = text.IndexOf('=', delimiterIndex); if (equalIndex == -1) { equalIndex = textLength; } } scanIndex = delimiterIndex + 1; } } } internal static partial class OwinHelpers { public static string GetHeader(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : string.Join(",", values); } public static IEnumerable GetHeaderSplit(IDictionary headers, string key) { string[] values = GetHeaderUnmodified(headers, key); return values == null ? null : GetHeaderSplitImplementation(values); } private static IEnumerable GetHeaderSplitImplementation(string[] values) { foreach (var segment in new HeaderSegmentCollection(values)) { if (segment.Data.HasValue) { yield return DeQuote(segment.Data.Value); } } } public static string[] GetHeaderUnmodified(IDictionary headers, string key) { if (headers == null) { throw new ArgumentNullException("headers"); } string[] values; return headers.TryGetValue(key, out values) ? values : null; } public static void SetHeader(IDictionary headers, string key, string value) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (string.IsNullOrWhiteSpace(value)) { headers.Remove(key); } else { headers[key] = new[] { value }; } } public static void SetHeaderJoined(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = new[] { string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } // Quote items that contain comas and are not already quoted. private static string QuoteIfNeeded(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Contains(',')) { if (value[0] != '"' || value[value.Length - 1] != '"') { value = '"' + value + '"'; } } return value; } private static string DeQuote(string value) { if (string.IsNullOrWhiteSpace(value)) { // Ignore } else if (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"') { value = value.Substring(1, value.Length - 2); } return value; } public static void SetHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (headers == null) { throw new ArgumentNullException("headers"); } if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } if (values == null || values.Length == 0) { headers.Remove(key); } else { headers[key] = values; } } public static void SetHeaderUnmodified(IDictionary headers, string key, IEnumerable values) { if (headers == null) { throw new ArgumentNullException("headers"); } headers[key] = values.ToArray(); } public static void AppendHeader(IDictionary headers, string key, string values) { if (string.IsNullOrWhiteSpace(values)) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeader(headers, key, values); } else { headers[key] = new[] { existing + "," + values }; } } public static void AppendHeaderJoined(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string existing = GetHeader(headers, key); if (existing == null) { SetHeaderJoined(headers, key, values); } else { headers[key] = new[] { existing + "," + string.Join(",", values.Select(value => QuoteIfNeeded(value))) }; } } public static void AppendHeaderUnmodified(IDictionary headers, string key, params string[] values) { if (values == null || values.Length == 0) { return; } string[] existing = GetHeaderUnmodified(headers, key); if (existing == null) { SetHeaderUnmodified(headers, key, values); } else { SetHeaderUnmodified(headers, key, existing.Concat(values)); } } } internal static partial class OwinHelpers { private static readonly Action AppendItemCallback = (name, value, state) => { var dictionary = (IDictionary>)state; List existing; if (!dictionary.TryGetValue(name, out existing)) { dictionary.Add(name, new List(1) { value }); } else { existing.Add(value); } }; private static readonly char[] AmpersandAndSemicolon = new[] { '&', ';' }; internal static IDictionary GetQuery(IOwinRequest request) { var query = request.Get>("Microsoft.Owin.Query#dictionary"); if (query == null) { query = new Dictionary(StringComparer.OrdinalIgnoreCase); request.Set("Microsoft.Owin.Query#dictionary", query); } string text = request.QueryString.Value; if (request.Get("Microsoft.Owin.Query#text") != text) { query.Clear(); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, AmpersandAndSemicolon, AppendItemCallback, accumulator); foreach (var kv in accumulator) { query.Add(kv.Key, kv.Value.ToArray()); } request.Set("Microsoft.Owin.Query#text", text); } return query; } internal static IFormCollection GetForm(string text) { IDictionary form = new Dictionary(StringComparer.OrdinalIgnoreCase); var accumulator = new Dictionary>(StringComparer.OrdinalIgnoreCase); ParseDelimited(text, new[] { '&' }, AppendItemCallback, accumulator); foreach (var kv in accumulator) { form.Add(kv.Key, kv.Value.ToArray()); } return new FormCollection(form); } internal static string GetJoinedValue(IDictionary store, string key) { string[] values = GetUnmodifiedValues(store, key); return values == null ? null : string.Join(",", values); } internal static string[] GetUnmodifiedValues(IDictionary store, string key) { if (store == null) { throw new ArgumentNullException("store"); } string[] values; return store.TryGetValue(key, out values) ? values : null; } } internal static partial class OwinHelpers { internal static string GetHost(IOwinRequest request) { IHeaderDictionary headers = request.Headers; string host = GetHeader(headers, "Host"); if (!string.IsNullOrWhiteSpace(host)) { return host; } string localIpAddress = request.LocalIpAddress ?? "localhost"; var localPort = request.Get(OwinConstants.CommonKeys.LocalPort); return string.IsNullOrWhiteSpace(localPort) ? localIpAddress : (localIpAddress + ":" + localPort); } } } namespace LibOwin { using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Security.Claims; using System.Security.Principal; using System.Text; using System.Threading; using System.Threading.Tasks; using LibOwin.Infrastructure; #if LIBOWIN_PUBLIC public partial class CookieOptions { } public partial class FormCollection { } public partial class HeaderDictionary { } public partial struct HostString { } public partial interface IFormCollection { } public partial interface IHeaderDictionary { } public partial interface IOwinContext { } public partial interface IOwinRequest { } public partial interface IOwinResponse { } public partial interface IReadableStringCollection { } public partial class OwinContext { } public partial class OwinRequest { } public partial class OwinResponse { } public partial struct PathString { } public partial struct QueryString { } public partial class ReadableStringCollection { } public partial class RequestCookieCollection { } public partial class ResponseCookieCollection { } public partial class IOwinResponseExtension { } #endif /// /// Options used to create a new cookie. /// partial class CookieOptions { /// /// Creates a default cookie with a path of '/'. /// public CookieOptions() { Path = "/"; } /// /// Gets or sets the domain to associate the cookie with. /// /// The domain to associate the cookie with. public string Domain { get; set; } /// /// Gets or sets the cookie path. /// /// The cookie path. public string Path { get; set; } /// /// Gets or sets the expiration date and time for the cookie. /// /// The expiration date and time for the cookie. public DateTime? Expires { get; set; } /// /// Gets or sets a value that indicates whether to transmit the cookie using Secure Sockets Layer (SSL)—that is, over HTTPS only. /// /// true to transmit the cookie only over an SSL connection (HTTPS); otherwise, false. public bool Secure { get; set; } /// /// Gets or sets a value that indicates whether a cookie is accessible by client-side script. /// /// true if a cookie is accessible by client-side script; otherwise, false. public bool HttpOnly { get; set; } } /// /// Contains the parsed form values. /// partial class FormCollection : ReadableStringCollection, IFormCollection { /// /// Initializes a new instance of the class. /// /// The store for the form. public FormCollection(IDictionary store) : base(store) {} } /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial class HeaderDictionary : IHeaderDictionary { /// /// Initializes a new instance of the class. /// /// The underlying data store. public HeaderDictionary(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Gets an that contains the keys in the ;. /// /// An that contains the keys in the . public ICollection Keys { get { return Store.Keys; } } /// /// /// public ICollection Values { get { return Store.Values; } } /// /// Gets the number of elements contained in the ;. /// /// The number of elements contained in the . public int Count { get { return Store.Count; } } /// /// Gets a value that indicates whether the is in read-only mode. /// /// true if the is in read-only mode; otherwise, false. public bool IsReadOnly { get { return Store.IsReadOnly; } } /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string this[string key] { get { return Get(key); } set { Set(key, value); } } /// /// Throws KeyNotFoundException if the key is not present. /// /// The header name. /// string[] IDictionary.this[string key] { get { return Store[key]; } set { Store[key] = value; } } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// Returns an enumerator that iterates through a collection. /// /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Get the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. public string Get(string key) { return OwinHelpers.GetHeader(Store, key); } /// /// Get the associated values from the collection without modification. /// /// The header name. /// the associated value from the collection without modification, or null if the key is not present. public IList GetValues(string key) { return OwinHelpers.GetHeaderUnmodified(Store, key); } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. public IList GetCommaSeparatedValues(string key) { IEnumerable values = OwinHelpers.GetHeaderSplit(Store, key); return values == null ? null : values.ToList(); } /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. public void Append(string key, string value) { OwinHelpers.AppendHeader(Store, key, value); } /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. public void AppendValues(string key, params string[] values) { OwinHelpers.AppendHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. public void AppendCommaSeparatedValues(string key, params string[] values) { OwinHelpers.AppendHeaderJoined(Store, key, values); } /// /// Sets a specific header value. /// /// The header name. /// The header value. public void Set(string key, string value) { OwinHelpers.SetHeader(Store, key, value); } /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. public void SetValues(string key, params string[] values) { OwinHelpers.SetHeaderUnmodified(Store, key, values); } /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. public void SetCommaSeparatedValues(string key, params string[] values) { OwinHelpers.SetHeaderJoined(Store, key, values); } /// /// Adds the given header and values to the collection. /// /// The header name. /// The header values. public void Add(string key, string[] value) { Store.Add(key, value); } /// /// Determines whether the contains a specific key. /// /// The key. /// true if the contains a specific key; otherwise, false. public bool ContainsKey(string key) { return Store.ContainsKey(key); } /// /// Removes the given header from the collection. /// /// The header name. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(string key) { return Store.Remove(key); } /// /// Retrieves a value from the dictionary. /// /// The header name. /// The value. /// true if the contains the key; otherwise, false. public bool TryGetValue(string key, out string[] value) { return Store.TryGetValue(key, out value); } /// /// Adds a new list of items to the collection. /// /// The item to add. public void Add(KeyValuePair item) { Store.Add(item); } /// /// Clears the entire list of objects. /// public void Clear() { Store.Clear(); } /// /// Returns a value indicating whether the specified object occurs within this collection. /// /// The item. /// true if the specified object occurs within this collection; otherwise, false. public bool Contains(KeyValuePair item) { return Store.Contains(item); } /// /// Copies the elements to a one-dimensional Array instance at the specified index. /// /// The one-dimensional Array that is the destination of the specified objects copied from the . /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) { Store.CopyTo(array, arrayIndex); } /// /// Removes the given item from the the collection. /// /// The item. /// true if the specified object was removed from the collection; otherwise, false. public bool Remove(KeyValuePair item) { return Store.Remove(item); } } /// /// Represents the host portion of a Uri can be used to construct Uri's properly formatted and encoded for use in /// HTTP headers. /// partial struct HostString : IEquatable { private readonly string _value; /// /// Creates a new HostString without modification. The value should be Unicode rather than punycode, and may have a port. /// IPv4 and IPv6 addresses are also allowed, and also may have ports. /// /// public HostString(string value) { _value = value; } /// /// Returns the original value from the constructor. /// public string Value { get { return _value; } } /// /// Returns the value as normalized by ToUriComponent(). /// /// public override string ToString() { return ToUriComponent(); } /// /// Returns the value properly formatted and encoded for use in a URI in a HTTP header. /// Any Unicode is converted to punycode. IPv6 addresses will have brackets added if they are missing. /// /// public string ToUriComponent() { int index; if (string.IsNullOrEmpty(_value)) { return string.Empty; } else if (_value.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port return _value; } else if ((index = _value.IndexOf(':')) >= 0 && index < _value.Length - 1 && _value.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons return "[" + _value + "]"; } else if (index >= 0) { // Has a port string port = _value.Substring(index); var mapping = new IdnMapping(); return mapping.GetAscii(_value, 0, index) + port; } else { var mapping = new IdnMapping(); return mapping.GetAscii(_value); } } /// /// Creates a new HostString from the given uri component. /// Any punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(string uriComponent) { if (!string.IsNullOrEmpty(uriComponent)) { int index; if (uriComponent.IndexOf('[') >= 0) { // IPv6 in brackets [::1], maybe with port } else if ((index = uriComponent.IndexOf(':')) >= 0 && index < uriComponent.Length - 1 && uriComponent.IndexOf(':', index + 1) >= 0) { // IPv6 without brackets ::1 is the only type of host with 2 or more colons } else if (uriComponent.IndexOf("xn--", StringComparison.Ordinal) >= 0) { // Contains punycode if (index >= 0) { // Has a port string port = uriComponent.Substring(index); IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent, 0, index) + port; } else { IdnMapping mapping = new IdnMapping(); uriComponent = mapping.GetUnicode(uriComponent); } } } return new HostString(uriComponent); } /// /// Creates a new HostString from the host and port of the give Uri instance. /// Punycode will be converted to Unicode. /// /// /// public static HostString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new HostString(uri.GetComponents( UriComponents.NormalizedHost | // Always convert punycode to Unicode. UriComponents.HostAndPort, UriFormat.Unescaped)); } /// /// Compares the equality of the Value property, ignoring case. /// /// /// public bool Equals(HostString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares against the given object only if it is a HostString. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is HostString && Equals((HostString)obj); } /// /// Gets a hash code for the value. /// /// public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(HostString left, HostString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(HostString left, HostString right) { return !left.Equals(right); } } /// /// Contains the parsed form values. /// partial interface IFormCollection : IReadableStringCollection {} /// /// Represents a wrapper for owin.RequestHeaders and owin.ResponseHeaders. /// partial interface IHeaderDictionary : IReadableStringCollection, IDictionary { /// /// Get or sets the associated value from the collection as a single string. /// /// The header name. /// the associated value from the collection as a single string or null if the key is not present. new string this[string key] { get; set; } /// /// Get the associated values from the collection separated into individual values. /// Quoted values will not be split, and the quotes will be removed. /// /// The header name. /// the associated values from the collection separated into individual values, or null if the key is not present. IList GetCommaSeparatedValues(string key); /// /// Add a new value. Appends to the header if already present /// /// The header name. /// The header value. void Append(string key, string value); /// /// Add new values. Each item remains a separate array entry. /// /// The header name. /// The header values. void AppendValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values with any existing values. /// /// The header name. /// The header values. void AppendCommaSeparatedValues(string key, params string[] values); /// /// Sets a specific header value. /// /// The header name. /// The header value. void Set(string key, string value); /// /// Sets the specified header values without modification. /// /// The header name. /// The header values. void SetValues(string key, params string[] values); /// /// Quotes any values containing comas, and then coma joins all of the values. /// /// The header name. /// The header values. void SetCommaSeparatedValues(string key, params string[] values); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinContext { /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. IOwinRequest Request { get; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. IOwinResponse Response { get; } /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. TextWriter TraceOutput { get; set; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinContext Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinRequest { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or set the HTTP method. /// /// The HTTP method. string Method { get; set; } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. string Scheme { get; set; } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. bool IsSecure { get; } /// /// Gets or set the Host header. May include the port. /// /// The Host header. HostString Host { get; set; } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. PathString PathBase { get; set; } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. PathString Path { get; set; } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. QueryString QueryString { get; set; } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. IReadableStringCollection Query { get; } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. Uri Uri { get; } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. string Protocol { get; set; } /// /// Gets the request headers. /// /// The request headers. IHeaderDictionary Headers { get; } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. RequestCookieCollection Cookies { get; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. string CacheControl { get; set; } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. string MediaType { get; set; } /// /// Gets or set the Accept header. /// /// The Accept header. string Accept { get; set; } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. Stream Body { get; set; } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. CancellationToken CallCancelled { get; set; } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. string LocalIpAddress { get; set; } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. int? LocalPort { get; set; } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. string RemoteIpAddress { get; set; } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. int? RemotePort { get; set; } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. ClaimsPrincipal User { get; set; } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. Task ReadFormAsync(); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinRequest Set(string key, T value); } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial interface IOwinResponse { /// /// Gets the OWIN environment. /// /// The OWIN environment. IDictionary Environment { get; } /// /// Gets the request context. /// /// The request context. IOwinContext Context { get; } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. int StatusCode { get; set; } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. string ReasonPhrase { get; set; } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. string Protocol { get; set; } /// /// Gets the response header collection. /// /// The response header collection. IHeaderDictionary Headers { get; } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. ResponseCookieCollection Cookies { get; } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. long? ContentLength { get; set; } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. string ContentType { get; set; } /// /// Gets or sets the Expires header. /// /// The Expires header. DateTimeOffset? Expires { get; set; } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. string ETag { get; set; } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. Stream Body { get; set; } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. void OnSendingHeaders(Action callback, object state); /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. void Redirect(string location); /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. void Write(string text); /// /// Writes the given bytes to the response body stream. /// /// The response data. void Write(byte[] data); /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. void Write(byte[] data, int offset, int count); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(string text); /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(string text, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, CancellationToken token); /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. Task WriteAsync(byte[] data, int offset, int count, CancellationToken token); /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. T Get(string key); /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. IOwinResponse Set(string key, T value); } /// /// Accessors for headers, query, forms, etc. /// partial interface IReadableStringCollection : IEnumerable> { /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string this[string key] { get; } // Joined /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// string Get(string key); // Joined /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// IList GetValues(string key); // Raw } internal static class OwinConstants { #region OWIN v1.0.0 - 3.2.1. Request Data // http://owin.org/spec/owin-1.0.0.html public const string RequestScheme = "owin.RequestScheme"; public const string RequestMethod = "owin.RequestMethod"; public const string RequestPathBase = "owin.RequestPathBase"; public const string RequestPath = "owin.RequestPath"; public const string RequestQueryString = "owin.RequestQueryString"; public const string RequestProtocol = "owin.RequestProtocol"; public const string RequestHeaders = "owin.RequestHeaders"; public const string RequestBody = "owin.RequestBody"; public const string RequestUser = "owin.RequestUser"; //owin 1.0.1 #endregion #region OWIN v1.0.0 - 3.2.2. Response Data // http://owin.org/spec/owin-1.0.0.html public const string ResponseStatusCode = "owin.ResponseStatusCode"; public const string ResponseReasonPhrase = "owin.ResponseReasonPhrase"; public const string ResponseProtocol = "owin.ResponseProtocol"; public const string ResponseHeaders = "owin.ResponseHeaders"; public const string ResponseBody = "owin.ResponseBody"; #endregion #region OWIN v1.0.0 - 3.2.3. Other Data // http://owin.org/spec/owin-1.0.0.html public const string CallCancelled = "owin.CallCancelled"; public const string OwinVersion = "owin.Version"; #endregion #region OWIN Keys for IAppBuilder.Properties internal static class Builder { public const string AddSignatureConversion = "builder.AddSignatureConversion"; public const string DefaultApp = "builder.DefaultApp"; } #endregion #region OWIN Key Guidelines and Common Keys - 6. Common keys // http://owin.org/spec/CommonKeys.html internal static class CommonKeys { public const string ClientCertificate = "ssl.ClientCertificate"; public const string RemoteIpAddress = "server.RemoteIpAddress"; public const string RemotePort = "server.RemotePort"; public const string LocalIpAddress = "server.LocalIpAddress"; public const string LocalPort = "server.LocalPort"; public const string IsLocal = "server.IsLocal"; public const string TraceOutput = "host.TraceOutput"; public const string Addresses = "host.Addresses"; public const string AppName = "host.AppName"; public const string Capabilities = "server.Capabilities"; public const string OnSendingHeaders = "server.OnSendingHeaders"; public const string OnAppDisposing = "host.OnAppDisposing"; public const string Scheme = "scheme"; public const string Host = "host"; public const string Port = "port"; public const string Path = "path"; } #endregion #region SendFiles v0.3.0 // http://owin.org/extensions/owin-SendFile-Extension-v0.3.0.htm internal static class SendFiles { // 3.1. Startup public const string Version = "sendfile.Version"; public const string Support = "sendfile.Support"; public const string Concurrency = "sendfile.Concurrency"; // 3.2. Per Request public const string SendAsync = "sendfile.SendAsync"; } #endregion #region Opaque v0.3.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class OpaqueConstants { // 3.1. Startup public const string Version = "opaque.Version"; // 3.2. Per Request public const string Upgrade = "opaque.Upgrade"; // 5. Consumption public const string Stream = "opaque.Stream"; // public const string Version = "opaque.Version"; // redundant, declared above public const string CallCancelled = "opaque.CallCancelled"; } #endregion #region WebSocket v0.4.0 // http://owin.org/extensions/owin-OpaqueStream-Extension-v0.3.0.htm internal static class WebSocket { // 3.1. Startup public const string Version = "websocket.Version"; // 3.2. Per Request public const string Accept = "websocket.Accept"; // 4. Accept public const string SubProtocol = "websocket.SubProtocol"; // 5. Consumption public const string SendAsync = "websocket.SendAsync"; public const string ReceiveAsync = "websocket.ReceiveAsync"; public const string CloseAsync = "websocket.CloseAsync"; // public const string Version = "websocket.Version"; // redundant, declared above public const string CallCancelled = "websocket.CallCancelled"; public const string ClientCloseStatus = "websocket.ClientCloseStatus"; public const string ClientCloseDescription = "websocket.ClientCloseDescription"; } #endregion #region Security v0.1.0 // http://owin.org/extensions/owin-Security-Extension-v0.1.0.htm internal static class Security { // 3.2. Per Request public const string User = "server.User"; public const string Authenticate = "security.Authenticate"; // 3.3. Response public const string SignIn = "security.SignIn"; public const string SignOut = "security.SignOut"; public const string SignOutProperties = "security.SignOutProperties"; public const string Challenge = "security.Challenge"; } #endregion } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinContext : IOwinContext { /// /// Create a new context with only request and response header collections. /// public OwinContext() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Create a new wrapper. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinContext(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; Request = new OwinRequest(environment); Response = new OwinResponse(environment); } /// /// Gets a wrapper exposing request specific properties. /// /// A wrapper exposing request specific properties. public virtual IOwinRequest Request { get; private set; } /// /// Gets a wrapper exposing response specific properties. /// /// A wrapper exposing response specific properties. public virtual IOwinResponse Response { get; private set; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets or sets the host.TraceOutput environment value. /// /// The host.TraceOutput TextWriter. public virtual TextWriter TraceOutput { get { return Get(OwinConstants.CommonKeys.TraceOutput); } set { Set(OwinConstants.CommonKeys.TraceOutput, value); } } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinContext Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinRequest : IOwinRequest { /// /// Create a new context with only request and response header collections. /// public OwinRequest() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Create a new environment wrapper exposing request properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinRequest(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or set the HTTP method. /// /// The HTTP method. public virtual string Method { get { return Get(OwinConstants.RequestMethod); } set { Set(OwinConstants.RequestMethod, value); } } /// /// Gets or set the HTTP request scheme from owin.RequestScheme. /// /// The HTTP request scheme from owin.RequestScheme. public virtual string Scheme { get { return Get(OwinConstants.RequestScheme); } set { Set(OwinConstants.RequestScheme, value); } } /// /// Returns true if the owin.RequestScheme is https. /// /// true if this request is using https; otherwise, false. public virtual bool IsSecure { get { return string.Equals(Scheme, Constants.Https, StringComparison.OrdinalIgnoreCase); } } /// /// Gets or set the Host header. May include the port. /// /// The Host header. public virtual HostString Host { get { return new HostString(OwinHelpers.GetHost(this)); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Host, value.Value); } } /// /// Gets or set the owin.RequestPathBase. /// /// The owin.RequestPathBase. public virtual PathString PathBase { get { return new PathString(Get(OwinConstants.RequestPathBase)); } set { Set(OwinConstants.RequestPathBase, value.Value); } } /// /// Gets or set the request path from owin.RequestPath. /// /// The request path from owin.RequestPath. public virtual PathString Path { get { return new PathString(Get(OwinConstants.RequestPath)); } set { Set(OwinConstants.RequestPath, value.Value); } } /// /// Gets or set the query string from owin.RequestQueryString. /// /// The query string from owin.RequestQueryString. public virtual QueryString QueryString { get { return new QueryString(Get(OwinConstants.RequestQueryString)); } set { Set(OwinConstants.RequestQueryString, value.Value); } } /// /// Gets the query value collection parsed from owin.RequestQueryString. /// /// The query value collection parsed from owin.RequestQueryString. public virtual IReadableStringCollection Query { get { return new ReadableStringCollection(OwinHelpers.GetQuery(this)); } } /// /// Gets the uniform resource identifier (URI) associated with the request. /// /// The uniform resource identifier (URI) associated with the request. public virtual Uri Uri { get { return new Uri(Scheme + "://" + Host + PathBase + Path + QueryString); } } /// /// Gets or set the owin.RequestProtocol. /// /// The owin.RequestProtocol. public virtual string Protocol { get { return Get(OwinConstants.RequestProtocol); } set { Set(OwinConstants.RequestProtocol, value); } } /// /// Gets the request headers. /// /// The request headers. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.RequestHeaders); } } /// /// Gets the collection of Cookies for this request. /// /// The collection of Cookies for this request. public RequestCookieCollection Cookies { get { return new RequestCookieCollection(OwinHelpers.GetCookies(this)); } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Cache-Control header. /// /// The Cache-Control header. public virtual string CacheControl { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.CacheControl); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.CacheControl, value); } } /// /// Gets or sets the Media-Type header. /// /// The Media-Type header. public virtual string MediaType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.MediaType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.MediaType, value); } } /// /// Gets or set the Accept header. /// /// The Accept header. public virtual string Accept { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Accept); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Accept, value); } } /// /// Gets or set the owin.RequestBody Stream. /// /// The owin.RequestBody Stream. public virtual Stream Body { get { return Get(OwinConstants.RequestBody); } set { Set(OwinConstants.RequestBody, value); } } /// /// Gets or sets the cancellation token for the request. /// /// The cancellation token for the request. public virtual CancellationToken CallCancelled { get { return Get(OwinConstants.CallCancelled); } set { Set(OwinConstants.CallCancelled, value); } } /// /// Gets or set the server.LocalIpAddress. /// /// The server.LocalIpAddress. public virtual string LocalIpAddress { get { return Get(OwinConstants.CommonKeys.LocalIpAddress); } set { Set(OwinConstants.CommonKeys.LocalIpAddress, value); } } /// /// Gets or set the server.LocalPort. /// /// The server.LocalPort. public virtual int? LocalPort { get { int value; if (int.TryParse(LocalPortString, out value)) { return value; } return null; } set { if (value.HasValue) { LocalPortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.LocalPort); } } } private string LocalPortString { get { return Get(OwinConstants.CommonKeys.LocalPort); } set { Set(OwinConstants.CommonKeys.LocalPort, value); } } /// /// Gets or set the server.RemoteIpAddress. /// /// The server.RemoteIpAddress. public virtual string RemoteIpAddress { get { return Get(OwinConstants.CommonKeys.RemoteIpAddress); } set { Set(OwinConstants.CommonKeys.RemoteIpAddress, value); } } /// /// Gets or set the server.RemotePort. /// /// The server.RemotePort. public virtual int? RemotePort { get { int value; if (int.TryParse(RemotePortString, out value)) { return value; } return null; } set { if (value.HasValue) { RemotePortString = value.Value.ToString(CultureInfo.InvariantCulture); } else { Environment.Remove(OwinConstants.CommonKeys.RemotePort); } } } private string RemotePortString { get { return Get(OwinConstants.CommonKeys.RemotePort); } set { Set(OwinConstants.CommonKeys.RemotePort, value); } } /// /// Gets or set the owin.RequestUser (or gets server.User for non-standard implementations). /// /// The server.User. public virtual ClaimsPrincipal User { get { var claimsPrincipal = Get(OwinConstants.RequestUser); return claimsPrincipal ?? Get(OwinConstants.Security.User) as ClaimsPrincipal; } set { Set(OwinConstants.RequestUser, value); } } /// /// Asynchronously reads and parses the request body as a form. /// /// The parsed form data. public async Task ReadFormAsync() { var form = Get("Microsoft.Owin.Form#collection"); if (form == null) { string text; // Don't close, it prevents re-winding. using (var reader = new StreamReader(Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 4 * 1024, leaveOpen: true)) { text = await reader.ReadToEndAsync(); } form = OwinHelpers.GetForm(text); Set("Microsoft.Owin.Form#collection", form); } return form; } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { object value; return Environment.TryGetValue(key, out value) ? (T)value : default(T); } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinRequest Set(string key, T value) { Environment[key] = value; return this; } } /// /// This wraps OWIN environment dictionary and provides strongly typed accessors. /// partial class OwinResponse : IOwinResponse { /// /// Create a new context with only request and response header collections. /// public OwinResponse() { IDictionary environment = new Dictionary(StringComparer.Ordinal); environment[OwinConstants.RequestHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); environment[OwinConstants.ResponseHeaders] = new Dictionary(StringComparer.OrdinalIgnoreCase); Environment = environment; } /// /// Creates a new environment wrapper exposing response properties. /// /// OWIN environment dictionary which stores state information about the request, response and relevant server state. public OwinResponse(IDictionary environment) { if (environment == null) { throw new ArgumentNullException("environment"); } Environment = environment; } /// /// Gets the OWIN environment. /// /// The OWIN environment. public virtual IDictionary Environment { get; private set; } /// /// Gets the request context. /// /// The request context. public virtual IOwinContext Context { get { return new OwinContext(Environment); } } /// /// Gets or sets the optional owin.ResponseStatusCode. /// /// The optional owin.ResponseStatusCode, or 200 if not set. public virtual int StatusCode { get { return Get(OwinConstants.ResponseStatusCode, 200); } set { Set(OwinConstants.ResponseStatusCode, value); } } /// /// Gets or sets the the optional owin.ResponseReasonPhrase. /// /// The the optional owin.ResponseReasonPhrase. public virtual string ReasonPhrase { get { return Get(OwinConstants.ResponseReasonPhrase); } set { Set(OwinConstants.ResponseReasonPhrase, value); } } /// /// Gets or sets the owin.ResponseProtocol. /// /// The owin.ResponseProtocol. public virtual string Protocol { get { return Get(OwinConstants.ResponseProtocol); } set { Set(OwinConstants.ResponseProtocol, value); } } /// /// Gets the response header collection. /// /// The response header collection. public virtual IHeaderDictionary Headers { get { return new HeaderDictionary(RawHeaders); } } private IDictionary RawHeaders { get { return Get>(OwinConstants.ResponseHeaders); } } /// /// Gets a collection used to manipulate the Set-Cookie header. /// /// A collection used to manipulate the Set-Cookie header. public virtual ResponseCookieCollection Cookies { get { return new ResponseCookieCollection(Headers); } } /// /// Gets or sets the Content-Length header. /// /// The Content-Length header. public virtual long? ContentLength { get { long value; if (long.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentLength), out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentLength, value.Value.ToString(CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.ContentLength); } } } /// /// Gets or sets the Content-Type header. /// /// The Content-Type header. public virtual string ContentType { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ContentType); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ContentType, value); } } /// /// Gets or sets the Expires header. /// /// The Expires header. public virtual DateTimeOffset? Expires { get { DateTimeOffset value; if (DateTimeOffset.TryParse(OwinHelpers.GetHeader(RawHeaders, Constants.Headers.Expires), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out value)) { return value; } return null; } set { if (value.HasValue) { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Expires, value.Value.ToString(Constants.HttpDateFormat, CultureInfo.InvariantCulture)); } else { RawHeaders.Remove(Constants.Headers.Expires); } } } /// /// Gets or sets the E-Tag header. /// /// The E-Tag header. public virtual string ETag { get { return OwinHelpers.GetHeader(RawHeaders, Constants.Headers.ETag); } set { OwinHelpers.SetHeader(RawHeaders, Constants.Headers.ETag, value); } } /// /// Gets or sets the owin.ResponseBody Stream. /// /// The owin.ResponseBody Stream. public virtual Stream Body { get { return Get(OwinConstants.ResponseBody); } set { Set(OwinConstants.ResponseBody, value); } } /// /// Registers for an event that fires when the response headers are sent. /// /// The callback method. /// The callback state. public virtual void OnSendingHeaders(Action callback, object state) { var onSendingHeaders = Get, object>>(OwinConstants.CommonKeys.OnSendingHeaders); if (onSendingHeaders == null) { throw new NotSupportedException(Resources.Exception_MissingOnSendingHeaders); } onSendingHeaders(callback, state); } /// /// Sets a 302 response status code and the Location header. /// /// The location where to redirect the client. public virtual void Redirect(string location) { StatusCode = 302; OwinHelpers.SetHeader(RawHeaders, Constants.Headers.Location, location); } /// /// Writes the given text to the response body stream using UTF-8. /// /// The response data. public virtual void Write(string text) { Write(Encoding.UTF8.GetBytes(text)); } /// /// Writes the given bytes to the response body stream. /// /// The response data. public virtual void Write(byte[] data) { Write(data, 0, data == null ? 0 : data.Length); } /// /// Writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. public virtual void Write(byte[] data, int offset, int count) { Body.Write(data, offset, count); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text) { return WriteAsync(text, CancellationToken.None); } /// /// Asynchronously writes the given text to the response body stream using UTF-8. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(string text, CancellationToken token) { return WriteAsync(Encoding.UTF8.GetBytes(text), token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data) { return WriteAsync(data, CancellationToken.None); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, CancellationToken token) { return WriteAsync(data, 0, data == null ? 0 : data.Length, token); } /// /// Asynchronously writes the given bytes to the response body stream. /// /// The response data. /// The zero-based byte offset in the parameter at which to begin copying bytes. /// The number of bytes to write. /// A token used to indicate cancellation. /// A Task tracking the state of the write operation. public virtual Task WriteAsync(byte[] data, int offset, int count, CancellationToken token) { return Body.WriteAsync(data, offset, count, token); } /// /// Gets a value from the OWIN environment, or returns default(T) if not present. /// /// The type of the value. /// The key of the value to get. /// The value with the specified key or the default(T) if not present. public virtual T Get(string key) { return Get(key, default(T)); } private T Get(string key, T fallback) { object value; return Environment.TryGetValue(key, out value) ? (T)value : fallback; } /// /// Sets the given key and value in the OWIN environment. /// /// The type of the value. /// The key of the value to set. /// The value to set. /// This instance. public virtual IOwinResponse Set(string key, T value) { Environment[key] = value; return this; } } /// /// Provides correct escaping for Path and PathBase values when needed to reconstruct a request or redirect URI string /// partial struct PathString : IEquatable { private static readonly Func EscapeDataString = Uri.EscapeDataString; /// /// Represents the empty path. This field is read-only. /// public static readonly PathString Empty = new PathString(String.Empty); private readonly string _value; /// /// Initialize the path string with a given value. This value must be in un-escaped format. Use /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format. /// /// The unescaped path to be assigned to the Value property. public PathString(string value) { if (!String.IsNullOrEmpty(value) && value[0] != '/') { throw new ArgumentException(Resources.Exception_PathMustStartWithSlash, "value"); } _value = value; } /// /// The unescaped path value /// public string Value { get { return _value; } } /// /// True if the path is not empty /// public bool HasValue { get { return !String.IsNullOrEmpty(_value); } } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public override string ToString() { return ToUriComponent(); } /// /// Provides the path string escaped in a way which is correct for combining into the URI representation. /// /// The escaped path value public string ToUriComponent() { if (HasValue) { if (RequiresEscaping(_value)) { // TODO: Measure the cost of this escaping and consider optimizing. return String.Join("/", _value.Split('/').Select(EscapeDataString)); } return _value; } return String.Empty; } // Very conservative, these characters do not need to be escaped in a path. private static bool RequiresEscaping(string value) { for (int i = 0; i < value.Length; i++) { char c = value[i]; // Check conservatively for safe characters. See http://www.ietf.org/rfc/rfc3986.txt bool safeChar = (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '/' || c == '-' || c == '_'); if (!safeChar) { return true; } } return false; } /// /// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a path. /// /// The escaped path as it appears in the URI format. /// The resulting PathString public static PathString FromUriComponent(string uriComponent) { // REVIEW: what is the exactly correct thing to do? return new PathString(Uri.UnescapeDataString(uriComponent)); } /// /// Returns an PathString given the path as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting PathString public static PathString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } // REVIEW: what is the exactly correct thing to do? return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)); } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// public bool StartsWithSegments(PathString other) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { return value1.Length == value2.Length || value1[value2.Length] == '/'; } return false; } /// /// Checks if this instance starts with or exactly matches the other instance. Only full segments are matched. /// /// /// Any remaining segments from this instance not included in the other instance. /// public bool StartsWithSegments(PathString other, out PathString remaining) { string value1 = Value ?? String.Empty; string value2 = other.Value ?? String.Empty; if (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase)) { if (value1.Length == value2.Length || value1[value2.Length] == '/') { remaining = new PathString(value1.Substring(value2.Length)); return true; } } remaining = Empty; return false; } /// /// Adds two PathString instances into a combined PathString value. /// /// The combined PathString value public PathString Add(PathString other) { return new PathString(Value + other.Value); } /// /// Combines a PathString and QueryString into the joined URI formatted string value. /// /// The joined URI formatted string value public string Add(QueryString other) { return ToUriComponent() + other.ToUriComponent(); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public bool Equals(PathString other) { return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase); } /// /// Compares this PathString value to another value using a specific StringComparison type /// /// The second PathString for comparison /// The StringComparison type to use /// True if both PathString values are equal public bool Equals(PathString other, StringComparison comparisonType) { return string.Equals(_value, other._value, comparisonType); } /// /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. /// /// The second PathString for comparison. /// True if both PathString values are equal public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is PathString && Equals((PathString)obj, StringComparison.OrdinalIgnoreCase); } /// /// Returns the hash code for the PathString value. The hash code is provided by the OrdinalIgnoreCase implementation. /// /// The hash code public override int GetHashCode() { return (_value != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are equal public static bool operator ==(PathString left, PathString right) { return left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Equals /// /// The left parameter /// The right parameter /// True if both PathString values are not equal public static bool operator !=(PathString left, PathString right) { return !left.Equals(right, StringComparison.OrdinalIgnoreCase); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static PathString operator +(PathString left, PathString right) { return left.Add(right); } /// /// Operator call through to Add /// /// The left parameter /// The right parameter /// The PathString combination of both values public static string operator +(PathString left, QueryString right) { return left.Add(right); } } /// /// Provides correct handling for QueryString value when needed to reconstruct a request or redirect URI string /// partial struct QueryString : IEquatable { /// /// Represents the empty query string. This field is read-only. /// public static readonly QueryString Empty = new QueryString(String.Empty); private readonly string _value; /// /// Initalize the query string with a given value. This value must be in escaped and delimited format without /// a leading '?' character. /// /// The query string to be assigned to the Value property. public QueryString(string value) { _value = value; } /// /// Initialize a query string with a single given parameter name and value. The value is /// /// The unencoded parameter name /// The unencoded parameter value public QueryString(string name, string value) { _value = Uri.EscapeDataString(name) + '=' + Uri.EscapeDataString(value); } /// /// The unescaped query string without the leading '?' character /// public string Value { get { return _value; } } /// /// True if the query string is not empty /// public bool HasValue { get { return !String.IsNullOrWhiteSpace(_value); } } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentally /// dangerous are escaped. /// /// The query string value public override string ToString() { return ToUriComponent(); } /// /// Provides the query string escaped in a way which is correct for combining into the URI representation. /// A leading '?' character will be prepended unless the Value is null or empty. Characters which are potentially /// dangerous are escaped. /// /// The query string value public string ToUriComponent() { // Escape things properly so System.Uri doesn't mis-interpret the data. return HasValue ? "?" + _value.Replace("#", "%23") : String.Empty; } /// /// Returns an QueryString given the query as it is escaped in the URI format. The string MUST NOT contain any /// value that is not a query. /// /// The escaped query as it appears in the URI format. /// The resulting QueryString public static QueryString FromUriComponent(string uriComponent) { if (String.IsNullOrEmpty(uriComponent)) { return new QueryString(string.Empty); } if (uriComponent[0] != '?') { throw new ArgumentException(Resources.Exception_QueryStringMustStartWithDelimiter, "uriComponent"); } return new QueryString(uriComponent.Substring(1)); } /// /// Returns an QueryString given the query as from a Uri object. Relative Uri objects are not supported. /// /// The Uri object /// The resulting QueryString public static QueryString FromUriComponent(Uri uri) { if (uri == null) { throw new ArgumentNullException("uri"); } return new QueryString(uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped)); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public bool Equals(QueryString other) { return string.Equals(_value, other._value); } /// /// Indicates whether the current instance is equal to the other instance. /// /// /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is QueryString && Equals((QueryString)obj); } /// /// Returns the hash code for this instance. /// /// public override int GetHashCode() { return (_value != null ? _value.GetHashCode() : 0); } /// /// Compares the two instances for equality. /// /// /// /// public static bool operator ==(QueryString left, QueryString right) { return left.Equals(right); } /// /// Compares the two instances for inequality. /// /// /// /// public static bool operator !=(QueryString left, QueryString right) { return !left.Equals(right); } } /// /// Accessors for query, forms, etc. /// partial class ReadableStringCollection : IReadableStringCollection { /// /// Create a new wrapper /// /// public ReadableStringCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string this[string key] { get { return Get(key); } } /// /// Get the associated value from the collection. Multiple values will be merged. /// Returns null if the key is not present. /// /// /// public string Get(string key) { return OwinHelpers.GetJoinedValue(Store, key); } /// /// Get the associated values from the collection in their original format. /// Returns null if the key is not present. /// /// /// public IList GetValues(string key) { string[] values; Store.TryGetValue(key, out values); return values; } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } /// /// A wrapper for the request Cookie header /// partial class RequestCookieCollection : IEnumerable> { /// /// Create a new wrapper /// /// public RequestCookieCollection(IDictionary store) { if (store == null) { throw new ArgumentNullException("store"); } Store = store; } private IDictionary Store { get; set; } /// /// Returns null rather than throwing KeyNotFoundException /// /// /// public string this[string key] { get { string value; Store.TryGetValue(key, out value); return value; } } /// /// /// /// public IEnumerator> GetEnumerator() { return Store.GetEnumerator(); } /// /// /// /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } internal static class Resources { internal const string Exception_MissingOnSendingHeaders = "The OWIN key 'server.OnSsoendingHeaders' is not available for this request."; internal const string Exception_PathMustStartWithSlash = "The path must start with a '/' followed by one or more characters."; internal const string Exception_QueryStringMustStartWithDelimiter = "The query string must start with a '?' unless null or empty."; } /// /// A wrapper for the response Set-Cookie header /// partial class ResponseCookieCollection { /// /// Create a new wrapper /// /// public ResponseCookieCollection(IHeaderDictionary headers) { if (headers == null) { throw new ArgumentNullException("headers"); } Headers = headers; } private IHeaderDictionary Headers { get; set; } /// /// Add a new cookie and value /// /// /// public void Append(string key, string value) { Headers.AppendValues(Constants.Headers.SetCookie, Uri.EscapeDataString(key) + "=" + Uri.EscapeDataString(value) + "; path=/"); } /// /// Add a new cookie /// /// /// /// public void Append(string key, string value, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); bool expiresHasValue = options.Expires.HasValue; string setCookieValue = string.Concat( Uri.EscapeDataString(key), "=", Uri.EscapeDataString(value ?? string.Empty), !domainHasValue ? null : "; domain=", !domainHasValue ? null : options.Domain, !pathHasValue ? null : "; path=", !pathHasValue ? null : options.Path, !expiresHasValue ? null : "; expires=", !expiresHasValue ? null : options.Expires.Value.ToString("ddd, dd-MMM-yyyy HH:mm:ss ", CultureInfo.InvariantCulture) + "GMT", !options.Secure ? null : "; secure", !options.HttpOnly ? null : "; HttpOnly"); Headers.AppendValues("Set-Cookie", setCookieValue); } /// /// Sets an expired cookie /// /// public void Delete(string key) { Func predicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); var deleteCookies = new[] { Uri.EscapeDataString(key) + "=; expires=Thu, 01-Jan-1970 00:00:00 GMT" }; IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues == null || existingValues.Count == 0) { Headers.SetValues(Constants.Headers.SetCookie, deleteCookies); } else { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !predicate(value)).Concat(deleteCookies).ToArray()); } } /// /// Sets an expired cookie /// /// /// public void Delete(string key, CookieOptions options) { if (options == null) { throw new ArgumentNullException("options"); } bool domainHasValue = !string.IsNullOrEmpty(options.Domain); bool pathHasValue = !string.IsNullOrEmpty(options.Path); Func rejectPredicate; if (domainHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("domain=" + options.Domain, StringComparison.OrdinalIgnoreCase) != -1; } else if (pathHasValue) { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase) && value.IndexOf("path=" + options.Path, StringComparison.OrdinalIgnoreCase) != -1; } else { rejectPredicate = value => value.StartsWith(key + "=", StringComparison.OrdinalIgnoreCase); } IList existingValues = Headers.GetValues(Constants.Headers.SetCookie); if (existingValues != null) { Headers.SetValues(Constants.Headers.SetCookie, existingValues.Where(value => !rejectPredicate(value)).ToArray()); } Append(key, string.Empty, new CookieOptions { Path = options.Path, Domain = options.Domain, Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), }); } } static partial class IOwinResponseExtension { /// /// Registers for an event that fires when the response headers are sent. /// /// The owin response /// The callback method. /// The callback state. public static void OnSendingHeaders(this IOwinResponse response, Action callback, T state) { if (response == null) { throw new ArgumentNullException("response"); } Action innerCallback = innerState => callback((T)innerState); response.OnSendingHeaders(innerCallback, state); } } } ================================================ FILE: src/chapter10/LoyaltyProgram/Bootstrapper.cs ================================================ namespace LoyaltyProgram { using System; using System.Collections.Generic; using System.Security.Claims; using Nancy; using Nancy.Bootstrapper; using Nancy.TinyIoc; using Nancy.Owin; public class Bootstrapper : DefaultNancyBootstrapper { protected override Func InternalConfiguration => NancyInternalConfiguration.WithOverrides(builder => builder.StatusCodeHandlers.Clear()); protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) { pipelines.OnError += (ctx, ex) => { // write to central log store return null; }; } } public class SetUser : IRequestStartup { public void Initialize(IPipelines pipelines, NancyContext context) => context.CurrentUser = context.GetOwinEnvironment()["pos-end-user"] as ClaimsPrincipal; } } ================================================ FILE: src/chapter10/LoyaltyProgram/Dockerfile ================================================ FROM microsoft/aspnet:1.0.0-rc1-update1 RUN printf "deb http://ftp.us.debian.org/debian jessie main\n" >> /etc/apt/sources.list RUN apt-get -qq update && apt-get install -qqy sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* COPY . /app WORKDIR /app RUN ["dnu", "restore"] EXPOSE 5000/tcp ENTRYPOINT ["dnx", "-p", "project.json", "Microsoft.AspNet.Server.Kestrel", "--server.urls", "http://0.0.0.0:5000"] ================================================ FILE: src/chapter10/LoyaltyProgram/LoyaltyProgram.xproj ================================================  14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 4bed3c45-e8c3-4345-a08f-249a39a256eb LoyaltyProgram .\obj .\bin\ 2.0 ================================================ FILE: src/chapter10/LoyaltyProgram/Program.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; namespace LoyaltyProgram { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup() .Build(); host.Run(); } } } ================================================ FILE: src/chapter10/LoyaltyProgram/Properties/launchSettings.json ================================================ { "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55343/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "LoyaltyProgram": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: src/chapter10/LoyaltyProgram/README.md ================================================ # Welcome to ASP.NET Core We've made some big updates in this release, so it’s **important** that you spend a few minutes to learn what’s new. You've created a new ASP.NET Core project. [Learn what's new](https://go.microsoft.com/fwlink/?LinkId=518016) ## This application consists of: * Sample pages using ASP.NET Core MVC * [Gulp](https://go.microsoft.com/fwlink/?LinkId=518007) and [Bower](https://go.microsoft.com/fwlink/?LinkId=518004) for managing client-side libraries * Theming using [Bootstrap](https://go.microsoft.com/fwlink/?LinkID=398939) ## How to * [Add a Controller and View](https://go.microsoft.com/fwlink/?LinkID=398600) * [Add an appsetting in config and access it in app.](https://go.microsoft.com/fwlink/?LinkID=699562) * [Manage User Secrets using Secret Manager.](https://go.microsoft.com/fwlink/?LinkId=699315) * [Use logging to log a message.](https://go.microsoft.com/fwlink/?LinkId=699316) * [Add packages using NuGet.](https://go.microsoft.com/fwlink/?LinkId=699317) * [Add client packages using Bower.](https://go.microsoft.com/fwlink/?LinkId=699318) * [Target development, staging or production environment.](https://go.microsoft.com/fwlink/?LinkId=699319) ## Overview * [Conceptual overview of what is ASP.NET Core](https://go.microsoft.com/fwlink/?LinkId=518008) * [Fundamentals of ASP.NET Core such as Startup and middleware.](https://go.microsoft.com/fwlink/?LinkId=699320) * [Working with Data](https://go.microsoft.com/fwlink/?LinkId=398602) * [Security](https://go.microsoft.com/fwlink/?LinkId=398603) * [Client side development](https://go.microsoft.com/fwlink/?LinkID=699321) * [Develop on different platforms](https://go.microsoft.com/fwlink/?LinkID=699322) * [Read more on the documentation site](https://go.microsoft.com/fwlink/?LinkID=699323) ## Run & Deploy * [Run your app](https://go.microsoft.com/fwlink/?LinkID=517851) * [Run tools such as EF migrations and more](https://go.microsoft.com/fwlink/?LinkID=517853) * [Publish to Microsoft Azure Web Apps](https://go.microsoft.com/fwlink/?LinkID=398609) We would love to hear your [feedback](https://go.microsoft.com/fwlink/?LinkId=518015) ================================================ FILE: src/chapter10/LoyaltyProgram/Startup.cs ================================================ namespace LoyaltyProgram { using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.IdentityModel.Tokens; using LibOwin; using Microsoft.Extensions.DependencyInjection; using Nancy.Owin; public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient(provider => UrlEncoder.Default); } public void Configure(IApplicationBuilder app) { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary(); var options = new JwtBearerOptions { Authority = "http://localhost:5001", RequireHttpsMetadata = false, Audience = "http://localhost:5001/resources", AutomaticAuthenticate = true }; app.UseJwtBearerAuthentication(options); app.UseOwin(buildFunc => { buildFunc(next => env => { var ctx = new OwinContext(env); var principal = ctx.Request.User; if (principal?.HasClaim("scope", "loyalty_program_write") ?? false) return next(env); ctx.Response.StatusCode = 403; return Task.FromResult(0); }); buildFunc(next => env => { var ctx = new OwinContext(env); var idToken = ctx.Request.User?.FindFirst("id_token"); if (idToken != null) { ctx.Set("pos-end-user-token", idToken); } return next(env); }); buildFunc(next => env => { var ctx = new OwinContext(env); if (ctx.Request.Headers.ContainsKey("pos-end-user")) { var tokenHandler = new JwtSecurityTokenHandler(); SecurityToken token; var userPrincipal = tokenHandler.ValidateToken(ctx.Request.Headers["pos-end-user"], new TokenValidationParameters(), out token); ctx.Set("pos-end-user", userPrincipal); } return next(env); }); buildFunc.UseNancy(); }); } } } ================================================ FILE: src/chapter10/LoyaltyProgram/UsersModule.cs ================================================ namespace LoyaltyProgram { using System.Linq; using System.Net; using System.Collections.Generic; using Nancy; using Nancy.ModelBinding; using HttpStatusCode = Nancy.HttpStatusCode; public class UsersModule : NancyModule { private static IDictionary registeredUsers = new Dictionary(); public UsersModule() : base("/users") { Get("/", _ => registeredUsers.Values); Get("/{userId:int}", parameters => { int userId = parameters.userId; if (registeredUsers.ContainsKey(userId)) return registeredUsers[userId]; else return HttpStatusCode.NotFound; }); Post("/", _ => { var newUser = this.Bind(); this.AddRegisteredUser(newUser); return this.CreatedResponse(newUser); }); Put("/{userId:int}", parameters => { int loggedInUserId; int.TryParse( this.Context .CurrentUser .Claims.FirstOrDefault(c => c.Type.StartsWith("id")) ?.Value.Split(':').Last() ?? "", out loggedInUserId); int userId = parameters.userId; if (loggedInUserId != userId) return HttpStatusCode.Forbidden; var updatedUser = this.Bind(); registeredUsers[userId] = updatedUser; return updatedUser; }); } private dynamic CreatedResponse(LoyaltyProgramUser newUser) { return this.Negotiate .WithStatusCode(HttpStatusCode.Created) .WithHeader("Location", this.Request.Url.SiteBase + "/users/" + newUser.Id) .WithModel(newUser); } private void AddRegisteredUser(LoyaltyProgramUser newUser) { var userId = registeredUsers.Count; newUser.Id = userId; registeredUsers[userId] = newUser; } } public class LoyaltyProgramUser { public int Id { get; set; } public string Name { get; set; } public int LoyaltyPoints { get; set; } public LoyaltyProgramSettings Settings { get; set; } } public class LoyaltyProgramSettings { public string[] Interests { get; set; } } } ================================================ FILE: src/chapter10/LoyaltyProgram/YamlSerializerDeserializer.cs ================================================ namespace LoyaltyProgram { using System; using System.Collections.Generic; using System.IO; using Nancy; using Nancy.ModelBinding; using Nancy.Responses.Negotiation; using YamlDotNet.Serialization; public class YamlBodyDeserializer : IBodyDeserializer { public bool CanDeserialize(MediaRange mediaRange, BindingContext context) => mediaRange.Subtype.ToString().EndsWith("yaml"); public object Deserialize(MediaRange mediaRange, Stream bodyStream, BindingContext context) { var yamlDeserializer = new Deserializer(); var reader = new StreamReader(bodyStream); return yamlDeserializer.Deserialize(reader, context.DestinationType); } } public class YamlBodySerializer : IResponseProcessor { public IEnumerable> ExtensionMappings { get { yield return new Tuple("yaml", new MediaRange("application/yaml")); } } public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context) => requestedMediaRange.Subtype.ToString().EndsWith("yaml") ? new ProcessorMatch { ModelResult = MatchResult.DontCare, RequestedContentTypeResult = MatchResult.NonExactMatch} : ProcessorMatch.None; public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context) => new Response { Contents = stream => { var yamlSerializer = new Serializer(); var streamWriter = new StreamWriter(stream); yamlSerializer.Serialize(streamWriter, model); streamWriter.Flush(); }, ContentType = "application/yaml" }; } } ================================================ FILE: src/chapter10/LoyaltyProgram/project.json ================================================ { "dependencies": { "Microsoft.NETCore.App": { "version": "1.0.0", "type": "platform" }, "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Owin": "1.0.0", "Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0", "System.Text.Encodings.Web": "4.0.0", "Nancy": "2.0.0-barneyrubble", "YamlDotNet": "3.8.0-pre233" }, "tools": { "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" }, "frameworks": { "netcoreapp1.0": { "imports": [ "dotnet5.6", "portable-net45+win8" ] } }, "buildOptions": { "emitEntryPoint": true, "preserveCompilationContext": true }, "runtimeOptions": { "gcServer": true }, "publishOptions": { "include": [ "wwwroot", "web.config" ] }, "scripts": { "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] }, "tooling": { "defaultNamespace": "LoyaltyProgram" } } ================================================ FILE: src/chapter10/LoyaltyProgram/web.config ================================================ ================================================ FILE: src/chapter10/ch10.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoyaltyProgram", "LoyaltyProgram\LoyaltyProgram.xproj", "{4BED3C45-E8C3-4345-A08F-249A39A256EB}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ApiGatewayMock", "ApiGatewayMock\ApiGatewayMock.xproj", "{FE109A02-BF1B-46FC-87C8-2A68781CE8A4}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Login", "Login\Login.xproj", "{F416D0B1-71EE-400E-91E2-FC2B047DA208}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BED3C45-E8C3-4345-A08F-249A39A256EB}.Release|Any CPU.Build.0 = Release|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE109A02-BF1B-46FC-87C8-2A68781CE8A4}.Release|Any CPU.Build.0 = Release|Any CPU {F416D0B1-71EE-400E-91E2-FC2B047DA208}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F416D0B1-71EE-400E-91E2-FC2B047DA208}.Debug|Any CPU.Build.0 = Debug|Any CPU {F416D0B1-71EE-400E-91E2-FC2B047DA208}.Release|Any CPU.ActiveCfg = Release|Any CPU {F416D0B1-71EE-400E-91E2-FC2B047DA208}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: src/chapter10/start-app.ps1 ================================================ start-process dotnet -ArgumentList run -WorkingDirectory ./IdentityServer start-process dotnet -ArgumentList run -WorkingDirectory ./LoyaltyProgram start-process dotnet -ArgumentList run -WorkingDirectory ./ApiGatewayMock ================================================ FILE: src/global.json ================================================ { "projects": [ "Chapter11" ], "sdk": { "version": "1.0.0-preview2-003121" } }