[
  {
    "path": ".gitignore",
    "content": "<<<<<<< HEAD\n# use glob syntax\nsyntax: glob\n\nwwwroot/app/**\nwwwroot/bin/**\nwwwroot/js/**\nwwwroot/scripts/**\nwwwroot/styles/**\nwwwroot/dist/main-client.*\nClientApp/dist/main-server.*\n\nnode_modules/**\n[Ss]cripts/[Aa]pp/*.js\n[Ss]cripts/[Aa]pp_old/*.js\n\n[Mm]igrations/**\n\n.tscache/**\n.vs/\n\n*.mwb.bak\t# MySQL Workbench .BAK file\n*.dbmdl\t\t# VS2010 Database Projects cache file\n*.obj\n*.pdb\n*.user\n*.aps\n*.pch\n*.vspscc\n*.vssscc\n*_i.c\n*_p.c\n*.ncb\n*.suo\n*.tlb\n*.tlh\n*.bak\n*.[Cc]ache\n*.ilk\n*.log\n*.lib\n*.sbr\n*.scc\n*.DotSettings\n[Bb]in\n[Dd]ebug*/**\nobj/\n[Rr]elease*/**\n_ReSharper*/**\nNDependOut/**\npackages/**\n[Tt]humbs.db\n[Tt]est[Rr]esult*\n[Bb]uild[Ll]og.*\n*.[Pp]ublish.xml\n*.resharper\n*.ncrunch*\n*.ndproj\n=======\n## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n**/Properties/launchSettings.json\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk \n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n# CodeRush\n.cr/\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output \nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder \n.mfractor/\n>>>>>>> 4584b353e67bb142e570857ccd8679e29581917f\n"
  },
  {
    "path": ".hgignore",
    "content": "# use glob syntax\nsyntax: glob\n\nwwwroot/app/**\nwwwroot/bin/**\nwwwroot/js/**\nwwwroot/scripts/**\nwwwroot/styles/**\nwwwroot/dist/main-client.*\nClientApp/dist/main-server.*\n\nnode_modules/**\n[Ss]cripts/[Aa]pp/*.js\n[Ss]cripts/[Aa]pp_old/*.js\n\n[Mm]igrations/**\n\n.tscache/**\n.vs/\n\n*.mwb.bak\t# MySQL Workbench .BAK file\n*.dbmdl\t\t# VS2010 Database Projects cache file\n*.obj\n*.pdb\n*.user\n*.aps\n*.pch\n*.vspscc\n*.vssscc\n*_i.c\n*_p.c\n*.ncb\n*.suo\n*.tlb\n*.tlh\n*.bak\n*.[Cc]ache\n*.ilk\n*.log\n*.lib\n*.sbr\n*.scc\n*.DotSettings\n[Bb]in\n[Dd]ebug*/**\nobj/\n[Rr]elease*/**\n_ReSharper*/**\nNDependOut/**\npackages/**\n[Tt]humbs.db\n[Tt]est[Rr]esult*\n[Bb]uild[Ll]og.*\n*.[Pp]ublish.xml\n*.resharper\n*.ncrunch*\n*.ndproj"
  },
  {
    "path": "All_Chapters.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_01\", \"Chapter_01\", \"{C7FB2977-FA5B-4F4A-BD6D-54A6D651C0E3}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_02\", \"Chapter_02\", \"{DA4FA59E-4B7D-4A0D-971F-B77BE4267C4C}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"HealthCheck\", \"Chapter_01\\HealthCheck\\HealthCheck.csproj\", \"{86C6803B-67C2-4116-8AD4-43944BDE93F7}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"HealthCheck\", \"Chapter_02\\HealthCheck\\HealthCheck.csproj\", \"{C511A9B1-AE75-4706-89C1-6398145F038A}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_03\", \"Chapter_03\", \"{F5B14D16-0229-4931-A303-D64EC9038864}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"HealthCheck\", \"Chapter_03\\HealthCheck\\HealthCheck.csproj\", \"{B5373428-037B-4CB1-99B4-446C28F5F097}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_04\", \"Chapter_04\", \"{FA1BC5A3-A4D2-4510-BD39-8E01E4862271}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_04\\WorldCities\\WorldCities.csproj\", \"{9C1F258A-287E-494C-A352-E1A97936F1B6}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_05\", \"Chapter_05\", \"{FFAA313C-D100-4C29-A218-D6CAA01512AF}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_05\\WorldCities\\WorldCities.csproj\", \"{D9380899-AE72-481D-B12A-6A91E5F47C24}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_06\", \"Chapter_06\", \"{86F733CD-5A19-4106-AF25-787F04E6D2E3}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_06\\WorldCities\\WorldCities.csproj\", \"{32275E7C-084A-46E4-9A7E-F18D6C85633C}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_07\", \"Chapter_07\", \"{1C38A4BD-8BD8-4B7B-86B4-867FBE9F00B4}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_07\\WorldCities\\WorldCities.csproj\", \"{5BCFD243-6BD5-46DD-84BD-D54BD015816B}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_08\", \"Chapter_08\", \"{DAE4DCA6-C7C0-41E6-819D-456A7F683FEE}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_08\\WorldCities\\WorldCities.csproj\", \"{74C81E44-7BDD-4229-8839-1982A5E0BFEF}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_09\", \"Chapter_09\", \"{BE6B5B8C-3C2F-4B03-AF12-DF27CE716CD0}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_09\\WorldCities\\WorldCities.csproj\", \"{AC1C3B40-F286-491B-9729-CF4DF97C3D14}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities.Tests\", \"Chapter_09\\WorldCities.Tests\\WorldCities.Tests.csproj\", \"{AA1F2009-1700-417C-B356-7AC41927DE73}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_10\", \"Chapter_10\", \"{F6DB5D7A-3A2B-4E7E-9973-89207916FDEB}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_10\\WorldCities\\WorldCities.csproj\", \"{3B525260-5C3F-4ABB-9DCE-263A0301155C}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities.Tests\", \"Chapter_10\\WorldCities.Tests\\WorldCities.Tests.csproj\", \"{FCFD8752-27D6-4349-8782-34D65F48AB7F}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"AuthSample\", \"Chapter_10\\AuthSample\\AuthSample.csproj\", \"{F0CB37D6-B777-41AF-BE67-9217A4C59318}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_11\", \"Chapter_11\", \"{0D0E3BFF-75C8-49D9-A00C-CED1A656C64D}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_11\\WorldCities\\WorldCities.csproj\", \"{137468D5-980E-41AD-8C6A-E93DAA8D44FA}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"HealthCheck\", \"Chapter_11\\HealthCheck\\HealthCheck.csproj\", \"{1969429B-96B6-4247-B38C-1D6A9019C757}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Chapter_12\", \"Chapter_12\", \"{2FE8A041-4CCF-4C77-83F4-A3594E705932}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"HealthCheck\", \"Chapter_12\\HealthCheck\\HealthCheck.csproj\", \"{83DB7FA3-A888-46A7-9DFA-DB9A30CB40DD}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_12\\WorldCities\\WorldCities.csproj\", \"{CC6B414C-356A-4D80-847F-478D2B32FC02}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Root items\", \"Root items\", \"{687B1BB3-B260-40C1-B1B5-BD9D0DAF0A38}\"\n\tProjectSection(SolutionItems) = preProject\n\t\tLICENSE = LICENSE\n\t\tREADME.md = README.md\n\tEndProjectSection\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{86C6803B-67C2-4116-8AD4-43944BDE93F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{86C6803B-67C2-4116-8AD4-43944BDE93F7}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{86C6803B-67C2-4116-8AD4-43944BDE93F7}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{86C6803B-67C2-4116-8AD4-43944BDE93F7}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{C511A9B1-AE75-4706-89C1-6398145F038A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{C511A9B1-AE75-4706-89C1-6398145F038A}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{C511A9B1-AE75-4706-89C1-6398145F038A}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{C511A9B1-AE75-4706-89C1-6398145F038A}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{B5373428-037B-4CB1-99B4-446C28F5F097}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{B5373428-037B-4CB1-99B4-446C28F5F097}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{B5373428-037B-4CB1-99B4-446C28F5F097}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{B5373428-037B-4CB1-99B4-446C28F5F097}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{9C1F258A-287E-494C-A352-E1A97936F1B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{9C1F258A-287E-494C-A352-E1A97936F1B6}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{9C1F258A-287E-494C-A352-E1A97936F1B6}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{9C1F258A-287E-494C-A352-E1A97936F1B6}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{D9380899-AE72-481D-B12A-6A91E5F47C24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{D9380899-AE72-481D-B12A-6A91E5F47C24}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{D9380899-AE72-481D-B12A-6A91E5F47C24}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{D9380899-AE72-481D-B12A-6A91E5F47C24}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{32275E7C-084A-46E4-9A7E-F18D6C85633C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{32275E7C-084A-46E4-9A7E-F18D6C85633C}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{32275E7C-084A-46E4-9A7E-F18D6C85633C}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{32275E7C-084A-46E4-9A7E-F18D6C85633C}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{5BCFD243-6BD5-46DD-84BD-D54BD015816B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{5BCFD243-6BD5-46DD-84BD-D54BD015816B}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{5BCFD243-6BD5-46DD-84BD-D54BD015816B}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{5BCFD243-6BD5-46DD-84BD-D54BD015816B}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{74C81E44-7BDD-4229-8839-1982A5E0BFEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{74C81E44-7BDD-4229-8839-1982A5E0BFEF}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{74C81E44-7BDD-4229-8839-1982A5E0BFEF}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{74C81E44-7BDD-4229-8839-1982A5E0BFEF}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{AC1C3B40-F286-491B-9729-CF4DF97C3D14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{AC1C3B40-F286-491B-9729-CF4DF97C3D14}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{AC1C3B40-F286-491B-9729-CF4DF97C3D14}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{AC1C3B40-F286-491B-9729-CF4DF97C3D14}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{AA1F2009-1700-417C-B356-7AC41927DE73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{AA1F2009-1700-417C-B356-7AC41927DE73}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{AA1F2009-1700-417C-B356-7AC41927DE73}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{AA1F2009-1700-417C-B356-7AC41927DE73}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{3B525260-5C3F-4ABB-9DCE-263A0301155C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{3B525260-5C3F-4ABB-9DCE-263A0301155C}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{3B525260-5C3F-4ABB-9DCE-263A0301155C}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{3B525260-5C3F-4ABB-9DCE-263A0301155C}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{FCFD8752-27D6-4349-8782-34D65F48AB7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{FCFD8752-27D6-4349-8782-34D65F48AB7F}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{FCFD8752-27D6-4349-8782-34D65F48AB7F}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{FCFD8752-27D6-4349-8782-34D65F48AB7F}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{F0CB37D6-B777-41AF-BE67-9217A4C59318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{F0CB37D6-B777-41AF-BE67-9217A4C59318}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{F0CB37D6-B777-41AF-BE67-9217A4C59318}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{F0CB37D6-B777-41AF-BE67-9217A4C59318}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{137468D5-980E-41AD-8C6A-E93DAA8D44FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{137468D5-980E-41AD-8C6A-E93DAA8D44FA}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{137468D5-980E-41AD-8C6A-E93DAA8D44FA}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{137468D5-980E-41AD-8C6A-E93DAA8D44FA}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{1969429B-96B6-4247-B38C-1D6A9019C757}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{1969429B-96B6-4247-B38C-1D6A9019C757}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{1969429B-96B6-4247-B38C-1D6A9019C757}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{1969429B-96B6-4247-B38C-1D6A9019C757}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{83DB7FA3-A888-46A7-9DFA-DB9A30CB40DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{83DB7FA3-A888-46A7-9DFA-DB9A30CB40DD}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{83DB7FA3-A888-46A7-9DFA-DB9A30CB40DD}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{83DB7FA3-A888-46A7-9DFA-DB9A30CB40DD}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{CC6B414C-356A-4D80-847F-478D2B32FC02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{CC6B414C-356A-4D80-847F-478D2B32FC02}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{CC6B414C-356A-4D80-847F-478D2B32FC02}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{CC6B414C-356A-4D80-847F-478D2B32FC02}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{86C6803B-67C2-4116-8AD4-43944BDE93F7} = {C7FB2977-FA5B-4F4A-BD6D-54A6D651C0E3}\n\t\t{C511A9B1-AE75-4706-89C1-6398145F038A} = {DA4FA59E-4B7D-4A0D-971F-B77BE4267C4C}\n\t\t{B5373428-037B-4CB1-99B4-446C28F5F097} = {F5B14D16-0229-4931-A303-D64EC9038864}\n\t\t{9C1F258A-287E-494C-A352-E1A97936F1B6} = {FA1BC5A3-A4D2-4510-BD39-8E01E4862271}\n\t\t{D9380899-AE72-481D-B12A-6A91E5F47C24} = {FFAA313C-D100-4C29-A218-D6CAA01512AF}\n\t\t{32275E7C-084A-46E4-9A7E-F18D6C85633C} = {86F733CD-5A19-4106-AF25-787F04E6D2E3}\n\t\t{5BCFD243-6BD5-46DD-84BD-D54BD015816B} = {1C38A4BD-8BD8-4B7B-86B4-867FBE9F00B4}\n\t\t{74C81E44-7BDD-4229-8839-1982A5E0BFEF} = {DAE4DCA6-C7C0-41E6-819D-456A7F683FEE}\n\t\t{AC1C3B40-F286-491B-9729-CF4DF97C3D14} = {BE6B5B8C-3C2F-4B03-AF12-DF27CE716CD0}\n\t\t{AA1F2009-1700-417C-B356-7AC41927DE73} = {BE6B5B8C-3C2F-4B03-AF12-DF27CE716CD0}\n\t\t{3B525260-5C3F-4ABB-9DCE-263A0301155C} = {F6DB5D7A-3A2B-4E7E-9973-89207916FDEB}\n\t\t{FCFD8752-27D6-4349-8782-34D65F48AB7F} = {F6DB5D7A-3A2B-4E7E-9973-89207916FDEB}\n\t\t{F0CB37D6-B777-41AF-BE67-9217A4C59318} = {F6DB5D7A-3A2B-4E7E-9973-89207916FDEB}\n\t\t{137468D5-980E-41AD-8C6A-E93DAA8D44FA} = {0D0E3BFF-75C8-49D9-A00C-CED1A656C64D}\n\t\t{1969429B-96B6-4247-B38C-1D6A9019C757} = {0D0E3BFF-75C8-49D9-A00C-CED1A656C64D}\n\t\t{83DB7FA3-A888-46A7-9DFA-DB9A30CB40DD} = {2FE8A041-4CCF-4C77-83F4-A3594E705932}\n\t\t{CC6B414C-356A-4D80-847F-478D2B32FC02} = {2FE8A041-4CCF-4C77-83F4-A3594E705932}\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_01/HealthCheck/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/README.md",
    "content": "# HealthCheck\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"HealthCheck\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\"src/assets\"],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"HealthCheck:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"HealthCheck:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"HealthCheck:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\"styles.css\"],\n            \"scripts\": [],\n            \"assets\": [\"src/assets\"]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\"src/tsconfig.app.json\", \"src/tsconfig.spec.json\"],\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"HealthCheck-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"HealthCheck:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"HealthCheck\"\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/package.json",
    "content": "{\n  \"name\": \"healthcheck\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run HealthCheck:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/app.component.html",
    "content": "<body>\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { CounterComponent } from './counter/counter.component';\nimport { FetchDataComponent } from './fetch-data/fetch-data.component';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavMenuComponent,\n    HomeComponent,\n    CounterComponent,\n    FetchDataComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    RouterModule.forRoot([\n      { path: '', component: HomeComponent, pathMatch: 'full' },\n      { path: 'counter', component: CounterComponent },\n      { path: 'fetch-data', component: FetchDataComponent },\n    ])\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/counter/counter.component.html",
    "content": "<h1>Counter</h1>\n\n<p>This is a simple example of an Angular component.</p>\n\n<p aria-live=\"polite\">Current count: <strong>{{ currentCount }}</strong></p>\n\n<button class=\"btn btn-primary\" (click)=\"incrementCounter()\">Increment</button>\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/counter/counter.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { CounterComponent } from './counter.component';\n\ndescribe('CounterComponent', () => {\n  let component: CounterComponent;\n  let fixture: ComponentFixture<CounterComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ CounterComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(CounterComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should display a title', async(() => {\n    const titleText = fixture.nativeElement.querySelector('h1').textContent;\n    expect(titleText).toEqual('Counter');\n  }));\n\n  it('should start with count 0, then increments by 1 when clicked', async(() => {\n    const countElement = fixture.nativeElement.querySelector('strong');\n    expect(countElement.textContent).toEqual('0');\n\n    const incrementButton = fixture.nativeElement.querySelector('button');\n    incrementButton.click();\n    fixture.detectChanges();\n    expect(countElement.textContent).toEqual('1');\n  }));\n});\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/counter/counter.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-counter-component',\n  templateUrl: './counter.component.html'\n})\nexport class CounterComponent {\n  public currentCount = 0;\n\n  public incrementCounter() {\n    this.currentCount++;\n  }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/fetch-data/fetch-data.component.html",
    "content": "<h1 id=\"tableLabel\">Weather forecast</h1>\n\n<p>This component demonstrates fetching data from the server.</p>\n\n<p *ngIf=\"!forecasts\"><em>Loading...</em></p>\n\n<table class='table table-striped' aria-labelledby=\"tableLabel\" *ngIf=\"forecasts\">\n  <thead>\n    <tr>\n      <th>Date</th>\n      <th>Temp. (C)</th>\n      <th>Temp. (F)</th>\n      <th>Summary</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr *ngFor=\"let forecast of forecasts\">\n      <td>{{ forecast.date }}</td>\n      <td>{{ forecast.temperatureC }}</td>\n      <td>{{ forecast.temperatureF }}</td>\n      <td>{{ forecast.summary }}</td>\n    </tr>\n  </tbody>\n</table>\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/fetch-data/fetch-data.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\n\n@Component({\n  selector: 'app-fetch-data',\n  templateUrl: './fetch-data.component.html'\n})\nexport class FetchDataComponent {\n  public forecasts: WeatherForecast[];\n\n  constructor(http: HttpClient, @Inject('BASE_URL') baseUrl: string) {\n    http.get<WeatherForecast[]>(baseUrl + 'weatherforecast').subscribe(result => {\n      this.forecasts = result;\n    }, error => console.error(error));\n  }\n}\n\ninterface WeatherForecast {\n  date: string;\n  temperatureC: number;\n  temperatureF: number;\n  summary: string;\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">HealthCheck</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/counter']\"\n              >Counter</a\n            >\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/fetch-data']\"\n              >Fetch data</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>HealthCheck</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n  </head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_01/HealthCheck/Controllers/WeatherForecastController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Extensions.Logging;\n\nnamespace HealthCheck.Controllers\n{\n    [ApiController]\n    [Route(\"[controller]\")]\n    public class WeatherForecastController : ControllerBase\n    {\n        private static readonly string[] Summaries = new[]\n        {\n            \"Freezing\", \"Bracing\", \"Chilly\", \"Cool\", \"Mild\", \"Warm\", \"Balmy\", \"Hot\", \"Sweltering\", \"Scorching\"\n        };\n\n        private readonly ILogger<WeatherForecastController> _logger;\n\n        public WeatherForecastController(ILogger<WeatherForecastController> logger)\n        {\n            _logger = logger;\n        }\n\n        [HttpGet]\n        public IEnumerable<WeatherForecast> Get()\n        {\n            var rng = new Random();\n            return Enumerable.Range(1, 5).Select(index => new WeatherForecast\n            {\n                Date = DateTime.Now.AddDays(index),\n                TemperatureC = rng.Next(-20, 55),\n                Summary = Summaries[rng.Next(Summaries.Length)]\n            })\n            .ToArray();\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/HealthCheck.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_01/HealthCheck/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_01/HealthCheck/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace HealthCheck.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/Pages/_ViewImports.cshtml",
    "content": "@using HealthCheck\n@namespace HealthCheck.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_01/HealthCheck/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace HealthCheck\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace HealthCheck\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews();\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles();\n            }\n\n            app.UseRouting();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/WeatherForecast.cs",
    "content": "using System;\n\nnamespace HealthCheck\n{\n    public class WeatherForecast\n    {\n        public DateTime Date { get; set; }\n\n        public int TemperatureC { get; set; }\n\n        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);\n\n        public string Summary { get; set; }\n    }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_01/HealthCheck/appsettings.json",
    "content": "{\n  \"Logging\": {\n      \"LogLevel\": {\n        \"Default\": \"Warning\"\n      }\n    },\n\"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "Chapter_01.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29102.190\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"HealthCheck\", \"Chapter_01\\HealthCheck\\HealthCheck.csproj\", \"{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{B140A870-A419-487C-A4D5-D1949CCFB411}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{B140A870-A419-487C-A4D5-D1949CCFB411}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{B140A870-A419-487C-A4D5-D1949CCFB411}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{B140A870-A419-487C-A4D5-D1949CCFB411}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {922DA531-077E-4848-833C-D0F1791E34E4}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_02/HealthCheck/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/README.md",
    "content": "# HealthCheck\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"HealthCheck\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\"src/assets\"],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"HealthCheck:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"HealthCheck:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"HealthCheck:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [ \"src/styles.css\" ],\n            \"scripts\": [],\n            \"assets\": [\"src/assets\"]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\"src/tsconfig.app.json\", \"src/tsconfig.spec.json\"],\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"HealthCheck-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"HealthCheck:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"HealthCheck\"\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/package.json",
    "content": "{\n  \"name\": \"healthcheck\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run HealthCheck:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/app/app.component.html",
    "content": "<body>\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavMenuComponent,\n    HomeComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    RouterModule.forRoot([\n      { path: '', component: HomeComponent, pathMatch: 'full' }\n    ])\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">HealthCheck</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>HealthCheck</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n  </head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  },\n  \"angularCompilerOptions\": {\n    \"strictMetadataEmit\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_02/HealthCheck/HealthCheck.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n    <RootNamespace>HealthCheck</RootNamespace>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Folder Include=\"Controllers\\\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_02/HealthCheck/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_02/HealthCheck/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace HealthCheck.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/Pages/_ViewImports.cshtml",
    "content": "@using HealthCheck\n@namespace HealthCheck.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_02/HealthCheck/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace HealthCheck\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace HealthCheck\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews();\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles(new StaticFileOptions()\n            {\n                OnPrepareResponse = (context) =>\n                {\n                    // Retrieve cache configuration from appsettings.json\n                    context.Context.Response.Headers[\"Cache-Control\"] =\n                        Configuration[\"StaticFiles:Headers:Cache-Control\"];\n                    context.Context.Response.Headers[\"Pragma\"] =\n                        Configuration[\"StaticFiles:Headers:Pragma\"];\n                    context.Context.Response.Headers[\"Expires\"] =\n                        Configuration[\"StaticFiles:Headers:Expires\"];\n                }\n            });\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles();\n            }\n\n            app.UseRouting();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  },\n  \"StaticFiles\": {\n    \"Headers\": {\n      \"Cache-Control\": \"no-cache, no-store\",\n      \"Pragma\": \"no-cache\",\n      \"Expires\": \"-1\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/appsettings.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\",\n  \"StaticFiles\": {\n    \"Headers\": {\n      \"Cache-Control\": \"max-age=3600\",\n      \"Pragma\": \"cache\",\n      \"Expires\": null\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_02/HealthCheck/libman.json",
    "content": "{\n  \"version\": \"1.0\",\n  \"defaultProvider\": \"cdnjs\",\n  \"libraries\": []\n}"
  },
  {
    "path": "Chapter_02/HealthCheck/wwwroot/test.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\" />\n    <title>Time for a test!</title>\n</head>\n<body>\n    Hello there!\n    <br /><br />\n    This is a test to see if the StaticFiles middleware is working properly.\n    <br /><br />\n    What about the client-side cache? Does it work or not?\n    <br /><br />\n    It seems like we can configure it: we disabled it during development,\n      and enabled it in production!\n</body>\n</html>"
  },
  {
    "path": "Chapter_02.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"HealthCheck\", \"Chapter_02\\HealthCheck\\HealthCheck.csproj\", \"{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_03/HealthCheck/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/README.md",
    "content": "# HealthCheck\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"HealthCheck\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\"src/assets\"],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"HealthCheck:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"HealthCheck:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"HealthCheck:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [ \"src/styles.css\" ],\n            \"scripts\": [],\n            \"assets\": [\"src/assets\"]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\"src/tsconfig.app.json\", \"src/tsconfig.spec.json\"],\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"HealthCheck-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"HealthCheck:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"HealthCheck\"\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/package.json",
    "content": "{\n  \"name\": \"healthcheck\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run HealthCheck:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/app.component.html",
    "content": "<body>\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { HealthCheckComponent } from './health-check/health-check.component';\n\n@NgModule({\n    declarations: [\n        AppComponent,\n        NavMenuComponent,\n        HomeComponent,\n        HealthCheckComponent\n    ],\n    imports: [\n        BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n        HttpClientModule,\n        FormsModule,\n        RouterModule.forRoot([\n            { path: '', component: HomeComponent, pathMatch: 'full' },\n            { path: 'health-check', component: HealthCheckComponent }\n        ])\n    ],\n    providers: [],\n    bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/health-check/health-check.component.css",
    "content": ".status {\n  font-weight: bold;\n}\n\n.Healthy {\n  color: green;\n}\n\n.Degraded {\n  color: orange;\n}\n\n.Unhealthy {\n  color: red;\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/health-check/health-check.component.html",
    "content": "<h1>Health Check</h1>\n\n<p>Here are the results of our health check:</p>\n\n<p *ngIf=\"!result\"><em>Loading...</em></p>\n\n<table class='table table-striped' aria-labelledby=\"tableLabel\" *ngIf=\"result\">\n  <thead>\n    <tr>\n      <th>Name</th>\n      <th>Response Time</th>\n      <th>Status</th>\n      <th>Description</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr *ngFor=\"let check of result.checks\">\n      <td>{{ check.name }}</td>\n      <td>{{ check.responseTime }}</td>\n      <td class=\"status {{ check.status }}\">{{ check.status }}</td>\n      <td>{{ check.description }}</td>\n    </tr>\n  </tbody>\n</table>\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/health-check/health-check.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\n\n@Component({\n    selector: 'app-health-check',\n    templateUrl: './health-check.component.html',\n    styleUrls: ['./health-check.component.css']\n})\nexport class HealthCheckComponent {\n  public result: Result;\n\n  constructor(\n    private http: HttpClient,\n    @Inject('BASE_URL') private baseUrl: string) {\n  }\n\n  ngOnInit() {\n    this.http.get<Result>(this.baseUrl + 'hc').subscribe(result => {\n      this.result = result;\n    }, error => console.error(error));\n  }\n}\n\ninterface Result {\n    checks: Check[];\n    totalStatus: string;\n    totalResponseTime: number;\n}\n\ninterface Check {\n    name: string;\n    status: string;\n    responseTime: number;\n}\n\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">HealthCheck</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/health-check']\"\n              >Health Check</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>HealthCheck</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n  </head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  },\n  \"angularCompilerOptions\": {\n    \"strictMetadataEmit\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_03/HealthCheck/CustomHealthCheckOptions.cs",
    "content": "﻿using Microsoft.AspNetCore.Diagnostics.HealthChecks;\nusing Microsoft.AspNetCore.Http;\nusing System.Linq;\nusing System.Net.Mime;\nusing System.Text.Json;\n\nnamespace HealthCheck\n{\n    public class CustomHealthCheckOptions : HealthCheckOptions\n    {\n        public CustomHealthCheckOptions() : base() \n        {\n            var jsonSerializerOptions = new JsonSerializerOptions() \n            { \n                WriteIndented = true \n            };\n\n            ResponseWriter = async (c, r) =>\n            {\n                c.Response.ContentType = MediaTypeNames.Application.Json;\n                c.Response.StatusCode = StatusCodes.Status200OK;\n\n                var result = JsonSerializer.Serialize(new\n                   {\n                      checks = r.Entries.Select(e => new\n                          {\n                              name = e.Key,\n                              responseTime = e.Value.Duration.TotalMilliseconds,\n                              status = e.Value.Status.ToString(),\n                              description = e.Value.Description\n                          }),\n                      totalStatus = r.Status,\n                      totalResponseTime = r.TotalDuration.TotalMilliseconds,\n                   }, jsonSerializerOptions);\n                await c.Response.WriteAsync(result);\n            };\n        }\n    }\n}"
  },
  {
    "path": "Chapter_03/HealthCheck/HealthCheck.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n    <RootNamespace>HealthCheck</RootNamespace>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Folder Include=\"Controllers\\\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_03/HealthCheck/ICMPHealthCheck.cs",
    "content": "﻿using Microsoft.Extensions.Diagnostics.HealthChecks;\nusing System;\nusing System.Net.NetworkInformation;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace HealthCheck\n{\n    public class ICMPHealthCheck : IHealthCheck\n    {\n        private string Host { get; set; }\n        private int Timeout { get; set; }\n\n        public ICMPHealthCheck(string host, int timeout)\n        {\n            Host = host;\n            Timeout = timeout;\n        }\n\n        public async Task<HealthCheckResult> CheckHealthAsync(\n            HealthCheckContext context,\n            CancellationToken cancellationToken = default)\n        {\n            try\n            {\n                using (var ping = new Ping())\n                {\n                    var reply = await ping.SendPingAsync(Host);\n\n                    switch (reply.Status)\n                    {\n                        case IPStatus.Success:\n                            var msg = String.Format(\n                                \"IMCP to {0} took {1} ms.\",\n                                Host,\n                                reply.RoundtripTime);\n\n                            return (reply.RoundtripTime > Timeout)\n                                ? HealthCheckResult.Degraded(msg)\n                                : HealthCheckResult.Healthy(msg);\n\n                        default:\n                            var err = String.Format(\n                                \"IMCP to {0} failed: {1}\",\n                                Host,\n                                reply.Status);\n                            return HealthCheckResult.Unhealthy(err);\n                    }\n                }\n            }\n            catch (Exception e)\n            {\n                var err = String.Format(\n                    \"IMCP to {0} failed: {1}\",\n                    Host,\n                    e.Message);\n                return HealthCheckResult.Unhealthy(err);\n            }\n        }\n    }\n}"
  },
  {
    "path": "Chapter_03/HealthCheck/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_03/HealthCheck/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace HealthCheck.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/Pages/_ViewImports.cshtml",
    "content": "@using HealthCheck\n@namespace HealthCheck.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_03/HealthCheck/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace HealthCheck\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace HealthCheck\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews();\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            services.AddHealthChecks()\n                .AddCheck(\"ICMP_01\", new ICMPHealthCheck(\"www.ryadel.com\", 100))\n                .AddCheck(\"ICMP_02\", new ICMPHealthCheck(\"www.google.com\", 100))\n                .AddCheck(\"ICMP_03\", new ICMPHealthCheck(\"www.does-not-exist.com\", 100));\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles(new StaticFileOptions()\n            {\n                OnPrepareResponse = (context) =>\n                {\n                    // Retrieve cache configuration from appsettings.json\n                    context.Context.Response.Headers[\"Cache-Control\"] =\n                        Configuration[\"StaticFiles:Headers:Cache-Control\"];\n                    context.Context.Response.Headers[\"Pragma\"] =\n                        Configuration[\"StaticFiles:Headers:Pragma\"];\n                    context.Context.Response.Headers[\"Expires\"] =\n                        Configuration[\"StaticFiles:Headers:Expires\"];\n                }\n            });\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles();\n            }\n\n            app.UseRouting();\n\n            app.UseHealthChecks(\"/hc\", new CustomHealthCheckOptions());\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  },\n  \"StaticFiles\": {\n    \"Headers\": {\n      \"Cache-Control\": \"no-cache, no-store\",\n      \"Pragma\": \"no-cache\",\n      \"Expires\": \"-1\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/appsettings.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\",\n  \"StaticFiles\": {\n    \"Headers\": {\n      \"Cache-Control\": \"max-age=3600\",\n      \"Pragma\": \"cache\",\n      \"Expires\": null\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_03/HealthCheck/libman.json",
    "content": "{\n  \"version\": \"1.0\",\n  \"defaultProvider\": \"cdnjs\",\n  \"libraries\": []\n}"
  },
  {
    "path": "Chapter_03/HealthCheck/wwwroot/test.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\" />\n    <title>Time for a test!</title>\n</head>\n<body>\n    Hello there!\n    <br /><br />\n    This is a test to see if the StaticFiles middleware is working properly.\n    <br /><br />\n    What about the client-side cache? Does it work or not?\n    <br /><br />\n    It seems like we can configure it: we disabled it during development,\n      and enabled it in production!\n</body>\n</html>"
  },
  {
    "path": "Chapter_03.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"HealthCheck\", \"Chapter_03\\HealthCheck\\HealthCheck.csproj\", \"{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_04/WorldCities/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/README.md",
    "content": "# WorldCities\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"WorldCities\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\"src/assets\"],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"WorldCities:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\"styles.css\"],\n            \"scripts\": [],\n            \"assets\": [\"src/assets\"]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\"src/tsconfig.app.json\", \"src/tsconfig.spec.json\"],\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"WorldCities-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"WorldCities:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"WorldCities\"\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/package.json",
    "content": "{\n  \"name\": \"worldcities\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run WorldCities:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/app/app.component.html",
    "content": "<body>\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavMenuComponent,\n    HomeComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    RouterModule.forRoot([\n      { path: '', component: HomeComponent, pathMatch: 'full' }\n    ])\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">WorldCities</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>WorldCities</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n  </head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_04/WorldCities/Controllers/CitiesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CitiesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CitiesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        [HttpGet]\n        public async Task<ActionResult<IEnumerable<City>>> GetCities()\n        {\n            return await _context.Cities.ToListAsync();\n        }\n\n        // GET: api/Cities/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<City>> GetCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            return city;\n        }\n\n        // PUT: api/Cities/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCity(int id, City city)\n        {\n            if (id != city.Id)\n            {\n                return BadRequest();\n            }\n\n            _context.Entry(city).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CityExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Cities\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<City>> PostCity(City city)\n        {\n            _context.Cities.Add(city);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCity\", new { id = city.Id }, city);\n        }\n\n        // DELETE: api/Cities/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<City>> DeleteCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            _context.Cities.Remove(city);\n            await _context.SaveChangesAsync();\n\n            return city;\n        }\n\n        private bool CityExists(int id)\n        {\n            return _context.Cities.Any(e => e.Id == id);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/Controllers/CountriesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CountriesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CountriesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Countries\n        [HttpGet]\n        public async Task<ActionResult<IEnumerable<Country>>> GetCountries()\n        {\n            return await _context.Countries.ToListAsync();\n        }\n\n        // GET: api/Countries/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<Country>> GetCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            return country;\n        }\n\n        // PUT: api/Countries/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCountry(int id, Country country)\n        {\n            if (id != country.Id)\n            {\n                return BadRequest();\n            }\n\n            _context.Entry(country).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CountryExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Countries\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<Country>> PostCountry(Country country)\n        {\n            _context.Countries.Add(country);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCountry\", new { id = country.Id }, country);\n        }\n\n        // DELETE: api/Countries/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<Country>> DeleteCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            _context.Countries.Remove(country);\n            await _context.SaveChangesAsync();\n\n            return country;\n        }\n\n        private bool CountryExists(int id)\n        {\n            return _context.Countries.Any(e => e.Id == id);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/Controllers/SeedController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing WorldCities.Data;\nusing OfficeOpenXml;\nusing System.IO;\nusing Microsoft.AspNetCore.Hosting;\nusing WorldCities.Data.Models;\nusing System.Text.Json;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]/[action]\")]\n    [ApiController]\n    public class SeedController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n        private readonly IWebHostEnvironment _env;\n\n        public SeedController(\n            ApplicationDbContext context, \n            IWebHostEnvironment env)\n        {\n            _context = context;\n            _env = env;\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> Import()\n        {\n            // NOTE: This method has been updated on 2020.09.13.\n            // The new version is more efficient than the code described in the book's Chapter 4.\n            // ref.: https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/issues/15\n\n            var path = Path.Combine(\n                _env.ContentRootPath,\n                String.Format(\"Data/Source/worldcities.xlsx\"));\n\n            using (var stream = new FileStream(\n                path,\n                FileMode.Open,\n                FileAccess.Read))\n            {\n                using (var ep = new ExcelPackage(stream))\n                {\n                    // get the first worksheet\n\n                    var ws = ep.Workbook.Worksheets[0];\n\n                    // initialize the record counters\n                    var nCountries = 0;\n                    var nCities = 0;\n\n                    #region Import all Countries\n                    // create a list containing all the countries already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCountries = _context.Countries.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n                        var name = row[nRow, 5].GetValue<string>();\n\n                        // does this country already exist in the database?\n                        if (lstCountries.Where(c => c.Name == name).Count() == 0)\n                        {\n                            // create the Country entity and fill it with xlsx data\n                            var country = new Country();\n                            country.Name = name;\n                            country.ISO2 = row[nRow, 6].GetValue<string>();\n                            country.ISO3 = row[nRow, 7].GetValue<string>();\n\n                            // add the new country to the DB context\n                            _context.Countries.Add(country);\n\n                            // store the country to retrieve its Id later on\n                            lstCountries.Add(country);\n\n                            // increment the counter\n                            nCountries++;\n                        }\n                    }\n\n                    // save all the countries into the Database\n                    if (nCountries > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    #region Import all Cities\n                    // create a list containing all the cities already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCities = _context.Cities.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n\n                        var name = row[nRow, 1].GetValue<string>();\n                        var name_ASCII = row[nRow, 2].GetValue<string>();\n                        var countryName = row[nRow, 5].GetValue<string>();\n                        var lat = row[nRow, 3].GetValue<decimal>();\n                        var lon = row[nRow, 4].GetValue<decimal>();\n                        // retrieve country and countryId\n                        var country = lstCountries.Where(c => c.Name == countryName)\n                            .FirstOrDefault();\n                        var countryId = country.Id;\n\n                        // does this city already exist in the database?\n                        if (lstCities.Where(\n                            c => c.Name == name\n                            && c.Lat == lat\n                            && c.Lon == lon\n                            && c.CountryId == countryId\n                        ).Count() == 0)\n                        {\n                            // create the City entity and fill it with xlsx data\n                            var city = new City();\n                            city.Name = name;\n                            city.Name_ASCII = name_ASCII;\n                            city.Lat = lat;\n                            city.Lon = lon;\n                            city.CountryId = countryId;\n\n                            // add the new city to the DB context\n                            _context.Cities.Add(city);\n\n                            // increment the counter\n                            nCities++;\n                        }\n                    }\n\n                    // save all the cities into the Database\n                    if (nCities > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    return new JsonResult(new\n                    {\n                        Cities = nCities,\n                        Countries = nCountries\n                    });\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Chapter_04/WorldCities/Data/ApplicationDbContext.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Data\n{\n    public class ApplicationDbContext : DbContext\n    {\n        #region Constructor\n        public ApplicationDbContext() : base()\n        {\n        }\n\n        public ApplicationDbContext(DbContextOptions options) : base(options)\n        {\n        }\n        #endregion Constructor\n\n        #region Methods\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            base.OnModelCreating(modelBuilder);\n\n            // Map Entity names to DB Table names\n            modelBuilder.Entity<City>().ToTable(\"Cities\");\n            modelBuilder.Entity<Country>().ToTable(\"Countries\");\n        }\n        #endregion Methods\n\n        #region Properties\n        public DbSet<City> Cities { get; set; }\n        public DbSet<Country> Countries { get; set; }\n        #endregion Properties\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/Data/Migrations/20191123030140_Initial.Designer.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    [Migration(\"20191123030140_Initial\")]\n    partial class Initial\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/Data/Migrations/20191123030140_Initial.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace WorldCities.Data.Migrations\n{\n    public partial class Initial : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.CreateTable(\n                name: \"Countries\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    ISO2 = table.Column<string>(nullable: true),\n                    ISO3 = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Countries\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"Cities\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    Name_ASCII = table.Column<string>(nullable: true),\n                    Lat = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    Lon = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    CountryId = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Cities\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_Cities_Countries_CountryId\",\n                        column: x => x.CountryId,\n                        principalTable: \"Countries\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_Cities_CountryId\",\n                table: \"Cities\",\n                column: \"CountryId\");\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.DropTable(\n                name: \"Cities\");\n\n            migrationBuilder.DropTable(\n                name: \"Countries\");\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/Data/Migrations/ApplicationDbContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    partial class ApplicationDbContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/Data/Models/City.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class City\n    {\n        #region Constructor\n        public City()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this City\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// City name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// City name (in ASCII format)\n        /// </summary>\n        public string Name_ASCII { get; set; }\n\n        /// <summary>\n        /// City latitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lat { get; set; }\n\n        /// <summary>\n        /// City longitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lon { get; set; }\n\n        /// <summary>\n        /// Country Id (foreign key)\n        /// </summary>\n        [ForeignKey(\"Country\")]\n        public int CountryId { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// The country related to this city.\n        /// </summary>\n        public virtual Country Country { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/Data/Models/Country.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class Country\n    {\n        #region Constructor\n        public Country()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this Country\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// Country name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-2 format)\n        /// </summary>\n        public string ISO2 { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-3 format)\n        /// </summary>\n        public string ISO3 { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// A list containing all the cities related to this country.\n        /// </summary>\n        public virtual List<City> Cities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_04/WorldCities/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/Pages/_ViewImports.cshtml",
    "content": "@using WorldCities\n@namespace WorldCities.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_04/WorldCities/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing WorldCities.Data;\n\nnamespace WorldCities\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews();\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            // Add EntityFramework support for SqlServer.\n            services.AddEntityFrameworkSqlServer();\n\n            // Add ApplicationDbContext.\n            services.AddDbContext<ApplicationDbContext>(options =>\n                options.UseSqlServer(\n                    Configuration.GetConnectionString(\"DefaultConnection\")\n                    )\n            );\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles();\n            }\n\n            app.UseRouting();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/WorldCities.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"EPPlus\" Version=\"4.5.3.2\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Debug\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" Version=\"3.1.0\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_04/WorldCities/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_04/WorldCities/appsettings.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"Server=localhost\\\\SQLEXPRESS;Database=WorldCities;User Id=WorldCities;Password=MyVeryOwn$721;Integrated Security=False;MultipleActiveResultSets=True\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "Chapter_04.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_04\\WorldCities\\WorldCities.csproj\", \"{485D17FC-2E33-462C-879A-D67B9DAF7388}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{485D17FC-2E33-462C-879A-D67B9DAF7388}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{485D17FC-2E33-462C-879A-D67B9DAF7388}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{485D17FC-2E33-462C-879A-D67B9DAF7388}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{485D17FC-2E33-462C-879A-D67B9DAF7388}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_05/WorldCities/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/README.md",
    "content": "# WorldCities\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"WorldCities\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"WorldCities:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"styles.css\"\n            ],\n            \"scripts\": [],\n            \"assets\": [\n              \"src/assets\"\n            ]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\n              \"src/tsconfig.app.json\",\n              \"src/tsconfig.spec.json\"\n            ],\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"WorldCities-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"WorldCities:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"WorldCities\"\n}"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/package.json",
    "content": "{\n  \"name\": \"worldcities\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run WorldCities:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/cdk\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/material\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"hammerjs\": \"2.0.8\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/angular-material.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatPaginatorModule } from '@angular/material/paginator';\nimport { MatSortModule } from '@angular/material/sort';\nimport { MatInputModule } from '@angular/material/input';\n\n@NgModule({\n  imports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule\n  ],\n  exports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule\n  ]\n})\n\nexport class AngularMaterialModule { }\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/app.component.html",
    "content": "<body>\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { CitiesComponent } from './cities/cities.component';\nimport { CountriesComponent } from './countries/countries.component';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { AngularMaterialModule } from './angular-material.module';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavMenuComponent,\n    HomeComponent,\n    CitiesComponent,\n    CountriesComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    RouterModule.forRoot([\n      { path: '', component: HomeComponent, pathMatch: 'full' },\n      { path: 'cities', component: CitiesComponent },\n      { path: 'countries', component: CountriesComponent }\n    ]),\n    BrowserAnimationsModule,\n    AngularMaterialModule\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/cities/_clientSidePagination/_README.txt",
    "content": "\nThe files contained in this folder have been added in 2020.09.13 to fix the \"client-side pagination\" technique of the MatPaginator component in book's Chapter 5\n(pages 224-226), before it gets replaced with the server-side pagination technique within that same chapter later on.\n\nIn order to make them work, do the following:\n\n- replace the cities.component.html and cities.components.ts files in the /ClientApp/sec/app/Cities/ folder with the corresponding files contained in this folder.\n- patch the GetCities() method in the /Controllers/CitiesController.cs file in the following way:\n\n        // GET: api/Cities\n        [HttpGet]\n        public async Task<ActionResult<IEnumerable<City>>> GetCities()\n        {\n            return await _context.Cities.ToListAsync();\n        }\n\nIMPORTANT: the \"client-side pagination\" technique is way less efficient than its \"server-side\" counterpart and has only been added there for demostration purposes:\nsee GitHub issue #16 ( https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/issues/16 ) for further details.\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/cities/_clientSidePagination/cities.component.html",
    "content": "<h1>Cities</h1>\n\n<p>Here's a list of cities: feel free to play with it.</p>\n\n<p *ngIf=\"!cities\"><em>Loading...</em></p>\n\n<table mat-table [dataSource]=\"cities\" class=\"mat-elevation-z8\"\n [hidden]=\"!cities\">\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef>ID</th>\n    <td mat-cell *matCellDef=\"let city\">{{city.id}}</td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef>Name</th>\n    <td mat-cell *matCellDef=\"let city\">{{city.name}}</td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"lat\">\n    <th mat-header-cell *matHeaderCellDef>Latitude</th>\n    <td mat-cell *matCellDef=\"let city\">{{city.lat}}</td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"lon\">\n    <th mat-header-cell *matHeaderCellDef>Longitude</th>\n    <td mat-cell *matCellDef=\"let city\">{{city.lon}}</td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!cities\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/cities/_clientSidePagination/cities.component.ts_sample",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator } from '@angular/material/paginator';\n\nimport { City } from './city';\n\n@Component({\n  selector: 'app-cities',\n  templateUrl: './cities.component.html',\n  styleUrls: ['./cities.component.css']\n})\nexport class CitiesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'lat', 'lon'];\n  public cities: MatTableDataSource<City>;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n\n  constructor(\n    private http: HttpClient,\n    @Inject('BASE_URL') private baseUrl: string) {\n  }\n\n  ngOnInit() {\n    this.http.get<City[]>(this.baseUrl + 'api/Cities')\n      .subscribe(result => {\n        this.cities = new MatTableDataSource<City>(result);\n        this.cities.paginator = this.paginator;\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/cities/cities.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/cities/cities.component.html",
    "content": "<h1>Cities</h1>\n\n<p>Here's a list of cities: feel free to play with it.</p>\n\n<p *ngIf=\"!cities\"><em>Loading...</em></p>\n\n<mat-form-field [hidden]=\"!cities\">\n    <input matInput (keyup)=\"loadData($event.target.value)\"\n        placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"cities\" class=\"mat-elevation-z8\" [hidden]=\"!cities\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.name}} </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"lat\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Latitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lat}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"lon\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Longitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lon}} </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!cities\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/cities/cities.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { City } from './city';\n\n@Component({\n  selector: 'app-cities',\n  templateUrl: './cities.component.html',\n  styleUrls: ['./cities.component.css']\n})\nexport class CitiesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'lat', 'lon'];\n  public cities: MatTableDataSource<City>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery:string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private http: HttpClient,\n    @Inject('BASE_URL') private baseUrl: string) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n        this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n    var url = this.baseUrl + 'api/Cities';\n    var params = new HttpParams()\n      .set(\"pageIndex\", event.pageIndex.toString())\n      .set(\"pageSize\", event.pageSize.toString())\n      .set(\"sortColumn\", (this.sort)\n          ? this.sort.active\n          : this.defaultSortColumn)\n      .set(\"sortOrder\", (this.sort)\n          ? this.sort.direction\n          : this.defaultSortOrder);\n\n    if (this.filterQuery) {\n      params = params\n          .set(\"filterColumn\", this.defaultFilterColumn)\n          .set(\"filterQuery\", this.filterQuery);\n    }\n\n    this.http.get<any>(url, { params })\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.cities = new MatTableDataSource<City>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/cities/city.ts",
    "content": "export interface City {\n    id: number;\n    name: string;\n    lat: number;\n    lon: number;\n    countryId: number;\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/countries/countries.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/countries/countries.component.html",
    "content": "<h1>Countries</h1>\n\n<p>Here's a list of countries: feel free to play with it.</p>\n\n<p *ngIf=\"!countries\"><em>Loading...</em></p>\n\n<mat-form-field [hidden]=\"!countries\">\n  <input matInput (keyup)=\"loadData($event.target.value)\"\n      placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"countries\" class=\"mat-elevation-z8\" [hidden]=\"!countries\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.name}} </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"iso2\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 2</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso2}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"iso3\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 3</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso3}} </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!countries\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/countries/countries.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { Country } from './country';\n\n@Component({\n  selector: 'app-countries',\n  templateUrl: './countries.component.html',\n  styleUrls: ['./countries.component.css']\n})\nexport class CountriesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'iso2', 'iso3'];\n  public countries: MatTableDataSource<Country>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private http: HttpClient,\n    @Inject('BASE_URL') private baseUrl: string) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n        this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n    var url = this.baseUrl + 'api/Countries';\n    var params = new HttpParams()\n      .set(\"pageIndex\", event.pageIndex.toString())\n      .set(\"pageSize\", event.pageSize.toString())\n      .set(\"sortColumn\", (this.sort)\n        ? this.sort.active\n        : this.defaultSortColumn)\n      .set(\"sortOrder\", (this.sort)\n        ? this.sort.direction\n        : this.defaultSortOrder);\n\n    if (this.filterQuery) {\n      params = params\n        .set(\"filterColumn\", this.defaultFilterColumn)\n        .set(\"filterQuery\", this.filterQuery);\n    }\n\n    this.http.get<any>(url, { params })\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.countries = new MatTableDataSource<Country>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/countries/country.ts",
    "content": "export interface Country {\n    id: number;\n    name: string;\n    iso2: string;\n    iso3: string;\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">WorldCities</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/cities']\"\n              >Cities</a\n            >\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/countries']\"\n              >Countries</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>WorldCities</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n      <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n</head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/main.ts",
    "content": "import 'hammerjs';\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_05/WorldCities/Controllers/CitiesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CitiesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CitiesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Cities/?pageIndex=0&pageSize=10\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<City>>> GetCities(\n                int pageIndex = 0,\n                int pageSize = 10,\n                string sortColumn = null,\n                string sortOrder = null,\n                string filterColumn = null,\n                string filterQuery = null)\n        {\n            return await ApiResult<City>.CreateAsync(\n                    _context.Cities,\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        // GET: api/Cities/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<City>> GetCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            return city;\n        }\n\n        // PUT: api/Cities/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCity(int id, City city)\n        {\n            if (id != city.Id)\n            {\n                return BadRequest();\n            }\n\n            _context.Entry(city).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CityExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Cities\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<City>> PostCity(City city)\n        {\n            _context.Cities.Add(city);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCity\", new { id = city.Id }, city);\n        }\n\n        // DELETE: api/Cities/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<City>> DeleteCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            _context.Cities.Remove(city);\n            await _context.SaveChangesAsync();\n\n            return city;\n        }\n\n        private bool CityExists(int id)\n        {\n            return _context.Cities.Any(e => e.Id == id);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/Controllers/CountriesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CountriesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CountriesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Countries/?pageIndex=0&pageSize=10\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<Country>>> GetCountries(\n                int pageIndex = 0,\n                int pageSize = 10,\n                string sortColumn = null,\n                string sortOrder = null,\n                string filterColumn = null,\n                string filterQuery = null)\n        {\n            return await ApiResult<Country>.CreateAsync(\n                    _context.Countries,\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n\n        // GET: api/Countries/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<Country>> GetCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            return country;\n        }\n\n        // PUT: api/Countries/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCountry(int id, Country country)\n        {\n            if (id != country.Id)\n            {\n                return BadRequest();\n            }\n\n            _context.Entry(country).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CountryExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Countries\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<Country>> PostCountry(Country country)\n        {\n            _context.Countries.Add(country);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCountry\", new { id = country.Id }, country);\n        }\n\n        // DELETE: api/Countries/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<Country>> DeleteCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            _context.Countries.Remove(country);\n            await _context.SaveChangesAsync();\n\n            return country;\n        }\n\n        private bool CountryExists(int id)\n        {\n            return _context.Countries.Any(e => e.Id == id);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/Controllers/SeedController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing WorldCities.Data;\nusing OfficeOpenXml;\nusing System.IO;\nusing Microsoft.AspNetCore.Hosting;\nusing WorldCities.Data.Models;\nusing System.Text.Json;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]/[action]\")]\n    [ApiController]\n    public class SeedController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n        private readonly IWebHostEnvironment _env;\n\n        public SeedController(\n            ApplicationDbContext context, \n            IWebHostEnvironment env)\n        {\n            _context = context;\n            _env = env;\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> Import()\n        {\n            // NOTE: This method has been updated on 2020.09.13.\n            // The new version is more efficient than the code described in the book's Chapter 4.\n            // ref.: https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/issues/15\n\n            var path = Path.Combine(\n                _env.ContentRootPath,\n                String.Format(\"Data/Source/worldcities.xlsx\"));\n\n            using (var stream = new FileStream(\n                path,\n                FileMode.Open,\n                FileAccess.Read))\n            {\n                using (var ep = new ExcelPackage(stream))\n                {\n                    // get the first worksheet\n\n                    var ws = ep.Workbook.Worksheets[0];\n\n                    // initialize the record counters\n                    var nCountries = 0;\n                    var nCities = 0;\n\n                    #region Import all Countries\n                    // create a list containing all the countries already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCountries = _context.Countries.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n                        var name = row[nRow, 5].GetValue<string>();\n\n                        // does this country already exist in the database?\n                        if (lstCountries.Where(c => c.Name == name).Count() == 0)\n                        {\n                            // create the Country entity and fill it with xlsx data\n                            var country = new Country();\n                            country.Name = name;\n                            country.ISO2 = row[nRow, 6].GetValue<string>();\n                            country.ISO3 = row[nRow, 7].GetValue<string>();\n\n                            // add the new country to the DB context\n                            _context.Countries.Add(country);\n\n                            // store the country to retrieve its Id later on\n                            lstCountries.Add(country);\n\n                            // increment the counter\n                            nCountries++;\n                        }\n                    }\n\n                    // save all the countries into the Database\n                    if (nCountries > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    #region Import all Cities\n                    // create a list containing all the cities already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCities = _context.Cities.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n\n                        var name = row[nRow, 1].GetValue<string>();\n                        var name_ASCII = row[nRow, 2].GetValue<string>();\n                        var countryName = row[nRow, 5].GetValue<string>();\n                        var lat = row[nRow, 3].GetValue<decimal>();\n                        var lon = row[nRow, 4].GetValue<decimal>();\n                        // retrieve country and countryId\n                        var country = lstCountries.Where(c => c.Name == countryName)\n                            .FirstOrDefault();\n                        var countryId = country.Id;\n\n                        // does this city already exist in the database?\n                        if (lstCities.Where(\n                            c => c.Name == name\n                            && c.Lat == lat\n                            && c.Lon == lon\n                            && c.CountryId == countryId\n                        ).Count() == 0)\n                        {\n                            // create the City entity and fill it with xlsx data\n                            var city = new City();\n                            city.Name = name;\n                            city.Name_ASCII = name_ASCII;\n                            city.Lat = lat;\n                            city.Lon = lon;\n                            city.CountryId = countryId;\n\n                            // add the new city to the DB context\n                            _context.Cities.Add(city);\n\n                            // increment the counter\n                            nCities++;\n                        }\n                    }\n\n                    // save all the cities into the Database\n                    if (nCities > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    return new JsonResult(new\n                    {\n                        Cities = nCities,\n                        Countries = nCountries\n                    });\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Chapter_05/WorldCities/Data/ApiResult.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Linq.Dynamic.Core;\nusing System.Reflection;\n\nnamespace WorldCities.Data\n{\n    public class ApiResult<T>\n    {\n        /// <summary>\n        /// Private constructor called by the CreateAsync method.\n        /// </summary>\n        private ApiResult(\n            List<T> data,\n            int count,\n            int pageIndex,\n            int pageSize,\n            string sortColumn,\n            string sortOrder,\n            string filterColumn,\n            string filterQuery)\n        {\n            Data = data;\n            PageIndex = pageIndex;\n            PageSize = pageSize;\n            TotalCount = count;\n            TotalPages = (int)Math.Ceiling(count / (double)pageSize);\n            SortColumn = sortColumn;\n            SortOrder = sortOrder;\n            FilterColumn = filterColumn;\n            FilterQuery = filterQuery;\n        }\n\n        #region Methods\n        /// <summary>\n        /// Pages, sorts and/or filters a IQueryable source.\n        /// </summary>\n        /// <param name=\"source\">An IQueryable source of generic type</param>\n        /// <param name=\"pageIndex\">Zero-based current page index (0 = first page)</param>\n        /// <param name=\"pageSize\">The actual size of each page</param>\n        /// <param name=\"sortColumn\">The sorting colum name</param>\n        /// <param name=\"sortOrder\">The sorting order (\"ASC\" or \"DESC\")</param>\n        /// <param name=\"filterColumn\">The filtering column name</param>\n        /// <param name=\"filterQuery\">The filtering query (value to lookup)</param>\n        /// <returns>\n        /// A object containing the IQueryable paged/sorted/filtered result \n        /// and all the relevant paging/sorting/filtering navigation info.\n        /// </returns>\n        public static async Task<ApiResult<T>> CreateAsync(\n            IQueryable<T> source,\n            int pageIndex,\n            int pageSize,\n            string sortColumn = null,\n            string sortOrder = null,\n            string filterColumn = null,\n            string filterQuery = null)\n        {\n            if (!String.IsNullOrEmpty(filterColumn)\n                && !String.IsNullOrEmpty(filterQuery)\n                && IsValidProperty(filterColumn))\n            {\n                source = source.Where(\n                    String.Format(\"{0}.Contains(@0)\",\n                    filterColumn),\n                    filterQuery);\n            }\n\n            var count = await source.CountAsync();\n\n            if (!String.IsNullOrEmpty(sortColumn)\n                && IsValidProperty(sortColumn))\n            {\n                sortOrder = !String.IsNullOrEmpty(sortOrder)\n                    && sortOrder.ToUpper() == \"ASC\"\n                    ? \"ASC\"\n                    : \"DESC\";\n                source = source.OrderBy(\n                    String.Format(\n                        \"{0} {1}\",\n                        sortColumn,\n                        sortOrder)\n                    );\n            }\n\n            source = source\n                .Skip(pageIndex * pageSize)\n                .Take(pageSize);\n\n            var data = await source.ToListAsync();\n\n            return new ApiResult<T>(\n                data,\n                count,\n                pageIndex,\n                pageSize,\n                sortColumn,\n                sortOrder,\n                filterColumn,\n                filterQuery);\n        }\n\n        /// <summary>\n        /// Checks if the given property name exists\n        /// to protect against SQL injection attacks\n        /// </summary>\n        public static bool IsValidProperty(\n            string propertyName,\n            bool throwExceptionIfNotFound = true)\n        {\n            var prop = typeof(T).GetProperty(\n                propertyName,\n                BindingFlags.IgnoreCase |\n                BindingFlags.Public |\n                BindingFlags.Static |\n                BindingFlags.Instance);\n            if (prop == null && throwExceptionIfNotFound)\n                throw new NotSupportedException(\n                    String.Format(\n                        \"ERROR: Property '{0}' does not exist.\",\n                        propertyName)\n                    );\n            return prop != null;\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// IQueryable data result to return.\n        /// </summary>\n        public List<T> Data { get; private set; }\n\n        /// <summary>\n        /// Zero-based index of current page.\n        /// </summary>\n        public int PageIndex { get; private set; }\n\n        /// <summary>\n        /// Number of items contained in each page.\n        /// </summary>\n        public int PageSize { get; private set; }\n\n        /// <summary>\n        /// Total items count\n        /// </summary>\n        public int TotalCount { get; private set; }\n\n        /// <summary>\n        /// Total pages count\n        /// </summary>\n        public int TotalPages { get; private set; }\n\n        /// <summary>\n        /// TRUE if the current page has a previous page, FALSE otherwise.\n        /// </summary>\n        public bool HasPreviousPage\n        {\n            get\n            {\n                return (PageIndex > 0);\n            }\n        }\n\n        /// <summary>\n        /// TRUE if the current page has a next page, FALSE otherwise.\n        /// </summary>\n        public bool HasNextPage\n        {\n            get\n            {\n                return ((PageIndex +1) < TotalPages);\n            }\n        }\n\n        /// <summary>\n        /// Sorting Column name (or null if none set)\n        /// </summary>\n        public string SortColumn { get; set; }\n\n        /// <summary>\n        /// Sorting Order (\"ASC\", \"DESC\" or null if none set)\n        /// </summary>\n        public string SortOrder { get; set; }\n\n        /// <summary>\n        /// Filter Column name (or null if none set)\n        /// </summary>\n        public string FilterColumn { get; set; }\n\n        /// <summary>\n        /// Filter Query string \n        /// (to be used within the given FilterColumn)\n        /// </summary>\n        public string FilterQuery { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/Data/ApplicationDbContext.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Data\n{\n    public class ApplicationDbContext : DbContext\n    {\n        #region Constructor\n        public ApplicationDbContext() : base()\n        {\n        }\n\n        public ApplicationDbContext(DbContextOptions options) : base(options)\n        {\n        }\n        #endregion Constructor\n\n        #region Methods\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            base.OnModelCreating(modelBuilder);\n\n            // Map Entity names to DB Table names\n            modelBuilder.Entity<City>().ToTable(\"Cities\");\n            modelBuilder.Entity<Country>().ToTable(\"Countries\");\n        }\n        #endregion Methods\n\n        #region Properties\n        public DbSet<City> Cities { get; set; }\n        public DbSet<Country> Countries { get; set; }\n        #endregion Properties\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/Data/Migrations/20191123030140_Initial.Designer.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    [Migration(\"20191123030140_Initial\")]\n    partial class Initial\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/Data/Migrations/20191123030140_Initial.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace WorldCities.Data.Migrations\n{\n    public partial class Initial : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.CreateTable(\n                name: \"Countries\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    ISO2 = table.Column<string>(nullable: true),\n                    ISO3 = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Countries\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"Cities\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    Name_ASCII = table.Column<string>(nullable: true),\n                    Lat = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    Lon = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    CountryId = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Cities\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_Cities_Countries_CountryId\",\n                        column: x => x.CountryId,\n                        principalTable: \"Countries\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_Cities_CountryId\",\n                table: \"Cities\",\n                column: \"CountryId\");\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.DropTable(\n                name: \"Cities\");\n\n            migrationBuilder.DropTable(\n                name: \"Countries\");\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/Data/Migrations/ApplicationDbContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    partial class ApplicationDbContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/Data/Models/City.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class City\n    {\n        #region Constructor\n        public City()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this City\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// City name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// City name (in ASCII format)\n        /// </summary>\n        public string Name_ASCII { get; set; }\n\n        /// <summary>\n        /// City latitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lat { get; set; }\n\n        /// <summary>\n        /// City longitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lon { get; set; }\n\n        /// <summary>\n        /// Country Id (foreign key)\n        /// </summary>\n        [ForeignKey(\"Country\")]\n        public int CountryId { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// The country related to this city.\n        /// </summary>\n        public virtual Country Country { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/Data/Models/Country.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class Country\n    {\n        #region Constructor\n        public Country()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this Country\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// Country name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-2 format)\n        /// </summary>\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-3 format)\n        /// </summary>\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// A list containing all the cities related to this country.\n        /// </summary>\n        public virtual List<City> Cities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_05/WorldCities/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/Pages/_ViewImports.cshtml",
    "content": "@using WorldCities\n@namespace WorldCities.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_05/WorldCities/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing System.Text.Json;\nusing WorldCities.Data;\n\nnamespace WorldCities\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews()\n                .AddJsonOptions(options => {\n                    // set this option to TRUE to indent the JSON output\n                    options.JsonSerializerOptions.WriteIndented = true;\n                    // set this option to NULL to use PascalCase instead of CamelCase (default)\n                    // options.JsonSerializerOptions.PropertyNamingPolicy = null;\n                });\n\n\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            // Add EntityFramework support for SqlServer.\n            services.AddEntityFrameworkSqlServer();\n\n            // Add ApplicationDbContext.\n            services.AddDbContext<ApplicationDbContext>(options =>\n                options.UseSqlServer(\n                    Configuration.GetConnectionString(\"DefaultConnection\")\n                    )\n            );\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles();\n            }\n\n            app.UseRouting();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/WorldCities.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"EPPlus\" Version=\"4.5.3.2\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Debug\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" Version=\"3.1.0\" />\n    <PackageReference Include=\"System.Linq.Dynamic.Core\" Version=\"1.0.20\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Compile Remove=\"Controllers\\CitiesController - Copy.cs\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_05/WorldCities/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_05/WorldCities/appsettings.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"Server=localhost\\\\SQLEXPRESS;Database=WorldCities;User Id=WorldCities;Password=MyVeryOwn$721;Integrated Security=False;MultipleActiveResultSets=True\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "Chapter_05.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_05\\WorldCities\\WorldCities.csproj\", \"{004D512C-9DD7-47C3-8AEF-59D813BD8FAA}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{004D512C-9DD7-47C3-8AEF-59D813BD8FAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{004D512C-9DD7-47C3-8AEF-59D813BD8FAA}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{004D512C-9DD7-47C3-8AEF-59D813BD8FAA}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{004D512C-9DD7-47C3-8AEF-59D813BD8FAA}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_06/WorldCities/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/README.md",
    "content": "# WorldCities\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"WorldCities\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"WorldCities:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"styles.css\"\n            ],\n            \"scripts\": [],\n            \"assets\": [\n              \"src/assets\"\n            ]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\n              \"src/tsconfig.app.json\",\n              \"src/tsconfig.spec.json\"\n            ],\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"WorldCities-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"WorldCities:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"WorldCities\"\n}"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/package.json",
    "content": "{\n  \"name\": \"worldcities\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run WorldCities:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/cdk\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/material\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"hammerjs\": \"2.0.8\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/angular-material.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatPaginatorModule } from '@angular/material/paginator';\nimport { MatSortModule } from '@angular/material/sort';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\n\n@NgModule({\n  imports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ],\n  exports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ]\n})\n\nexport class AngularMaterialModule { }\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/app.component.html",
    "content": "<body>\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { CitiesComponent } from './cities/cities.component';\nimport { CityEditComponent } from './cities/city-edit.component';\nimport { CountriesComponent } from './countries/countries.component';\nimport { CountryEditComponent } from './countries/country-edit.component';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { AngularMaterialModule } from './angular-material.module';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavMenuComponent,\n    HomeComponent,\n    CitiesComponent,\n    CityEditComponent,\n    CountriesComponent,\n    CountryEditComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    RouterModule.forRoot([\n      { path: '', component: HomeComponent, pathMatch: 'full' },\n      { path: 'cities', component: CitiesComponent },\n      { path: 'city/:id', component: CityEditComponent },\n      { path: 'city', component: CityEditComponent },\n      { path: 'countries', component: CountriesComponent },\n      { path: 'country/:id', component: CountryEditComponent },\n      { path: 'country', component: CountryEditComponent }\n    ]),\n    BrowserAnimationsModule,\n    AngularMaterialModule,\n    ReactiveFormsModule\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/cities/cities.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/cities/cities.component.html",
    "content": "<h1>Cities</h1>\n\n<p>Here's a list of cities: feel free to play with it.</p>\n\n<p *ngIf=\"!cities\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"cities\">\n  <button type=\"submit\"\n          [routerLink]=\"['/city']\"\n          class=\"btn btn-success\">\n      Add a new City\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!cities\">\n    <input matInput (keyup)=\"loadData($event.target.value)\"\n        placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"cities\" class=\"mat-elevation-z8\" [hidden]=\"!cities\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/city', city.id]\">{{city.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"lat\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Latitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lat}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"lon\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Longitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lon}} </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!cities\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/cities/cities.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { City } from './city';\n\n@Component({\n  selector: 'app-cities',\n  templateUrl: './cities.component.html',\n  styleUrls: ['./cities.component.css']\n})\nexport class CitiesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'lat', 'lon'];\n  public cities: MatTableDataSource<City>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private http: HttpClient,\n    @Inject('BASE_URL') private baseUrl: string) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n    var url = this.baseUrl + 'api/Cities';\n    var params = new HttpParams()\n      .set(\"pageIndex\", event.pageIndex.toString())\n      .set(\"pageSize\", event.pageSize.toString())\n      .set(\"sortColumn\", (this.sort)\n        ? this.sort.active\n        : this.defaultSortColumn)\n      .set(\"sortOrder\", (this.sort)\n        ? this.sort.direction\n        : this.defaultSortOrder);\n\n    if (this.filterQuery) {\n      params = params\n        .set(\"filterColumn\", this.defaultFilterColumn)\n        .set(\"filterQuery\", this.filterQuery);\n    }\n\n    this.http.get<any>(url, { params })\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.cities = new MatTableDataSource<City>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/cities/city-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/cities/city-edit.component.html",
    "content": "<div class=\"city-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !city\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n\n        <div *ngIf=\"form.invalid && form.errors && form.errors?.isDupeCity\"\n             class=\"alert alert-danger\">\n              <strong>ERROR</strong>:\n              A city with the same <i>name</i>, <i>lat</i>,\n              <i>lon</i> and <i>country</i> already exists.\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"name\">City name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"City name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"form.get('name').invalid &&\n                 (form.get('name').dirty || form.get('name').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"lat\">City latitude:</label>\n            <br />\n            <input type=\"text\" id=\"lat\"\n                   formControlName=\"lat\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('lat').invalid &&\n                 (form.get('lat').dirty || form.get('lat').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lat').errors?.required\">\n                  Latitude is required.\n                </div>\n            </div>\n        </div>\n\n\n        <div class=\"form-group\">\n            <label for=\"lon\">City longitude:</label>\n            <br />\n            <input type=\"text\" id=\"lon\"\n                   formControlName=\"lon\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('lon').invalid &&\n                 (form.get('lon').dirty || form.get('lon').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lon').errors?.required\">\n                  Longitude is required.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"countryId\">Country:</label>\n            <br />\n            <mat-form-field *ngIf=\"countries\">\n              <mat-label>Select a Country...</mat-label>\n              <mat-select id=\"countryId\" formControlName=\"countryId\">\n                <mat-option *ngFor=\"let country of countries\" [value]=\"country.id\">\n                  {{country.name}}\n                </mat-option>\n              </mat-select>\n            </mat-form-field>\n\n            <div *ngIf=\"form.get('countryId').invalid &&\n                 (form.get('countryId').dirty || form.get('countryId').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('countryId').errors?.required\">\n                  Please select a Country.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/cities']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/cities/city-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormControl, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nimport { City } from './City';\nimport { Country } from './../countries/Country';\n\n@Component({\n  selector: 'app-city-edit',\n  templateUrl: './city-edit.component.html',\n  styleUrls: ['./city-edit.component.css']\n})\nexport class CityEditComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  city: City;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new city,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  // the countries array for the select\n  countries: Country[];\n\n  constructor(\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private http: HttpClient,\n    @Inject('BASE_URL') private baseUrl: string) {\n  }\n\n  ngOnInit() {\n    this.form = new FormGroup({\n      name: new FormControl('', Validators.required),\n      lat: new FormControl('', Validators.required),\n      lon: new FormControl('', Validators.required),\n      countryId: new FormControl('', Validators.required)\n    }, null, this.isDupeCity());\n\n    this.loadData();\n  }\n\n  loadData() {\n\n    // load countries\n    this.loadCountries();\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the city from the server\n      var url = this.baseUrl + \"api/cities/\" + this.id;\n      this.http.get<City>(url).subscribe(result => {\n        this.city = result;\n        this.title = \"Edit - \" + this.city.name;\n\n        // update the form with the city value\n        this.form.patchValue(this.city);\n      }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new City\";\n    }\n  }\n\n  loadCountries() {\n    // fetch all the countries from the server\n    var url = this.baseUrl + \"api/countries?pageIndex=0&pageSize=9999&sortColumn=name\";\n    var params = new HttpParams()\n      .set(\"pageIndex\", \"0\")\n      .set(\"pageSize\", \"9999\")\n      .set(\"sortColumn\", \"name\");\n\n    this.http.get<any>(url, { params }).subscribe(result => {\n      this.countries = result.data;\n    }, error => console.error(error));\n  }\n\n  onSubmit() {\n\n    var city = (this.id) ? this.city : <City>{};\n\n    city.name = this.form.get(\"name\").value;\n    city.lat = +this.form.get(\"lat\").value;\n    city.lon = +this.form.get(\"lon\").value;\n    city.countryId = +this.form.get(\"countryId\").value;\n\n    if (this.id) {\n      // EDIT mode\n\n      var url = this.baseUrl + \"api/cities/\" + this.city.id;\n      this.http\n        .put<City>(url, city)\n        .subscribe(result => {\n\n          console.log(\"City \" + city.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      var url = this.baseUrl + \"api/cities\";\n      this.http\n        .post<City>(url, city)\n        .subscribe(result => {\n\n            console.log(\"City \" + result.id + \" has been created.\");\n\n            // go back to cities view\n            this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeCity(): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n\n      var city = <City>{};\n      city.id = (this.id) ? this.id : 0; \n      city.name = this.form.get(\"name\").value;\n      city.lat = +this.form.get(\"lat\").value;\n      city.lon = +this.form.get(\"lon\").value;\n      city.countryId = +this.form.get(\"countryId\").value;\n\n      var url = this.baseUrl + \"api/cities/IsDupeCity\";\n      return this.http.post<boolean>(url, city).pipe(map(result => {\n\n          return (result ? { isDupeCity: true } : null);\n      }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/cities/city.ts",
    "content": "export interface City {\n    id: number;\n    name: string;\n    lat: number;\n    lon: number;\n    countryId: number;\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/countries/countries.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/countries/countries.component.html",
    "content": "<h1>Countries</h1>\n\n<p>Here's a list of countries: feel free to play with it.</p>\n\n<p *ngIf=\"!countries\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"countries\">\n  <button type=\"button\"\n          [routerLink]=\"['/country']\"\n          class=\"btn btn-success\">\n      Add a new Country\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!countries\">\n  <input matInput (keyup)=\"loadData($event.target.value)\"\n      placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"countries\" class=\"mat-elevation-z8\" [hidden]=\"!countries\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let country\">\n      <a [routerLink]=\"['/country', country.id]\">{{country.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"iso2\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 2</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso2}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"iso3\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 3</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso3}} </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!countries\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/countries/countries.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { Country } from './country';\n\n@Component({\n  selector: 'app-countries',\n  templateUrl: './countries.component.html',\n  styleUrls: ['./countries.component.css']\n})\nexport class CountriesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'iso2', 'iso3'];\n  public countries: MatTableDataSource<Country>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private http: HttpClient,\n    @Inject('BASE_URL') private baseUrl: string) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n    var url = this.baseUrl + 'api/Countries';\n    var params = new HttpParams()\n      .set(\"pageIndex\", event.pageIndex.toString())\n      .set(\"pageSize\", event.pageSize.toString())\n      .set(\"sortColumn\", (this.sort)\n        ? this.sort.active\n        : this.defaultSortColumn)\n      .set(\"sortOrder\", (this.sort)\n        ? this.sort.direction\n        : this.defaultSortOrder);\n\n    if (this.filterQuery) {\n      params = params\n        .set(\"filterColumn\", this.defaultFilterColumn)\n        .set(\"filterQuery\", this.filterQuery);\n    }\n\n    this.http.get<any>(url, { params })\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.countries = new MatTableDataSource<Country>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/countries/country-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/countries/country-edit.component.html",
    "content": "<div class=\"country-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !country\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n     \n        <div class=\"form-group\">\n            <label for=\"name\">Country name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"Country name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"form.get('name').invalid &&\n                 (form.get('name').dirty || form.get('name').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n                <div *ngIf=\"form.get('name').errors?.isDupeField\">\n                  Name does already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"iso2\">ISO 3166-1 ALPHA-2 Country Code (2 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso2\"\n                   formControlName=\"iso2\" required\n                   placeholder=\"2 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso2').invalid &&\n                 (form.get('iso2').dirty || form.get('iso2').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso2').errors?.required\">\n                  ISO 3166-1 ALPHA-2 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.pattern\">\n                  ISO 3166-1 ALPHA-2 country code requires 2 letters.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-2 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n              <div class=\"form-group\">\n            <label for=\"iso3\">ISO 3166-1 ALPHA-3 Country Code (3 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso3\"\n                   formControlName=\"iso3\" required\n                   placeholder=\"3 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso3').invalid &&\n                 (form.get('iso3').dirty || form.get('iso3').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso3').errors?.required\">\n                  ISO 3166-1 ALPHA-3 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.pattern\">\n                  ISO 3166-1 ALPHA-3 country code requires 3 letters.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-3 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/countries']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/countries/country-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormBuilder, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { map } from 'rxjs/operators';\nimport { Observable } from 'rxjs';\n\nimport { Country } from './../countries/Country';\n\n@Component({\n    selector: 'app-country-edit',\n    templateUrl: './country-edit.component.html',\n    styleUrls: ['./country-edit.component.css']\n})\nexport class CountryEditComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  country: Country;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new country,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  constructor(\n    private fb: FormBuilder,\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private http: HttpClient,\n    @Inject('BASE_URL') private baseUrl: string) {\n      this.loadData();\n  }\n\n  ngOnInit() {\n    this.form = this.fb.group({\n      name: ['',\n        Validators.required,\n        this.isDupeField(\"name\")\n      ],\n      iso2: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{2}/)\n        ],\n        this.isDupeField(\"iso2\")\n      ],\n      iso3: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{3}/)\n        ],\n        this.isDupeField(\"iso3\")\n      ]\n    });\n\n    this.loadData();\n  }\n\n  loadData() {\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the country from the server\n      var url = this.baseUrl + \"api/countries/\" + this.id;\n      this.http.get<Country>(url).subscribe(result => {\n          this.country = result;\n          this.title = \"Edit - \" + this.country.name;\n\n          // update the form with the country value\n          this.form.patchValue(this.country);\n      }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new Country\";\n    }\n  }\n\n  onSubmit() {\n\n    var country = (this.id) ? this.country : <Country>{};\n\n    country.name = this.form.get(\"name\").value;\n    country.iso2 = this.form.get(\"iso2\").value;\n    country.iso3 = this.form.get(\"iso3\").value;\n\n    if (this.id) {\n      // EDIT mode\n\n      var url = this.baseUrl + \"api/countries/\" + this.country.id;\n      this.http\n        .put<Country>(url, country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + country.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      var url = this.baseUrl + \"api/countries\";\n      this.http\n        .post<Country>(url, country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeField(fieldName: string): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n\n      var params = new HttpParams()\n        .set(\"countryId\", (this.id) ? this.id.toString() : \"0\")\n        .set(\"fieldName\", fieldName)\n        .set(\"fieldValue\", control.value);\n      var url = this.baseUrl + \"api/countries/IsDupeField\";\n      return this.http.post<boolean>(url, null, { params })\n        .pipe(map(result => {\n          return (result ? { isDupeField: true } : null);\n      }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/countries/country.ts",
    "content": "export interface Country {\n    id: number;\n    name: string;\n    iso2: string;\n    iso3: string;\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">WorldCities</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/cities']\"\n              >Cities</a\n            >\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/countries']\"\n              >Countries</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>WorldCities</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n      <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n</head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/main.ts",
    "content": "import 'hammerjs';\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_06/WorldCities/Controllers/CitiesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CitiesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CitiesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Cities/?pageIndex=0&pageSize=10\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<City>>> GetCities(\n                int pageIndex = 0,\n                int pageSize = 10,\n                string sortColumn = null,\n                string sortOrder = null,\n                string filterColumn = null,\n                string filterQuery = null)\n        {\n            return await ApiResult<City>.CreateAsync(\n                    _context.Cities,\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        // GET: api/Cities/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<City>> GetCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            return city;\n        }\n\n        // PUT: api/Cities/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCity(int id, City city)\n        {\n            if (id != city.Id)\n            {\n                return BadRequest();\n            }\n\n            //var sourceCity = _context.Cities.Where(i => i.Id == city.Id).FirstOrDefault();\n            //if (sourceCity == null) return BadRequest();\n            //sourceCity.Name = city.Name;\n            //sourceCity.Lat = city.Lat;\n            //sourceCity.Lon = city.Lon;\n\n            _context.Entry(city).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CityExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Cities\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<City>> PostCity(City city)\n        {\n            _context.Cities.Add(city);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCity\", new { id = city.Id }, city);\n        }\n\n        // DELETE: api/Cities/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<City>> DeleteCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            _context.Cities.Remove(city);\n            await _context.SaveChangesAsync();\n\n            return city;\n        }\n\n        private bool CityExists(int id)\n        {\n            return _context.Cities.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeCity\")]\n        public bool IsDupeCity(City city)\n        {\n            return _context.Cities.Any(\n                e => e.Name == city.Name\n                && e.Lat == city.Lat\n                && e.Lon == city.Lon\n                && e.CountryId == city.CountryId\n                && e.Id != city.Id);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/Controllers/CountriesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Dynamic.Core;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CountriesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CountriesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Countries/?pageIndex=0&pageSize=10\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<Country>>> GetCountries(\n                int pageIndex = 0,\n                int pageSize = 10,\n                string sortColumn = null,\n                string sortOrder = null,\n                string filterColumn = null,\n                string filterQuery = null)\n        {\n            return await ApiResult<Country>.CreateAsync(\n                    _context.Countries,\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n\n        // GET: api/Countries/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<Country>> GetCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            return country;\n        }\n\n        // PUT: api/Countries/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCountry(int id, Country country)\n        {\n            if (id != country.Id)\n            {\n                return BadRequest();\n            }\n\n            _context.Entry(country).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CountryExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Countries\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<Country>> PostCountry(Country country)\n        {\n            _context.Countries.Add(country);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCountry\", new { id = country.Id }, country);\n        }\n\n        // DELETE: api/Countries/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<Country>> DeleteCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            _context.Countries.Remove(country);\n            await _context.SaveChangesAsync();\n\n            return country;\n        }\n\n        private bool CountryExists(int id)\n        {\n            return _context.Countries.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeField\")]\n        public bool IsDupeField(\n            int countryId, \n            string fieldName, \n            string fieldValue)\n        {\n            // Standard approach(using strongly-typed LAMBA expressions)\n            //switch (fieldName)\n            //{\n            //    case \"name\":\n            //        return _context.Countries.Any(\n            //            c => c.Name == fieldValue && c.Id != countryId);\n            //    case \"iso2\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO2 == fieldValue && c.Id != countryId);\n            //    case \"iso3\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO3 == fieldValue && c.Id != countryId);\n            //    default:\n            //        return false;\n            //}\n\n            // Dynamic approach (using System.Linq.Dynamic.Core)\n            return (ApiResult<Country>.IsValidProperty(fieldName, true))\n                ? _context.Countries.Any(\n                    String.Format(\"{0} == @0 && Id != @1\", fieldName),\n                    fieldValue,\n                    countryId)\n                : false;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/Controllers/SeedController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing WorldCities.Data;\nusing OfficeOpenXml;\nusing System.IO;\nusing Microsoft.AspNetCore.Hosting;\nusing WorldCities.Data.Models;\nusing System.Text.Json;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]/[action]\")]\n    [ApiController]\n    public class SeedController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n        private readonly IWebHostEnvironment _env;\n\n        public SeedController(\n            ApplicationDbContext context, \n            IWebHostEnvironment env)\n        {\n            _context = context;\n            _env = env;\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> Import()\n        {\n            // NOTE: This method has been updated on 2020.09.13.\n            // The new version is more efficient than the code described in the book's Chapter 4.\n            // ref.: https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/issues/15\n\n            var path = Path.Combine(\n                _env.ContentRootPath,\n                String.Format(\"Data/Source/worldcities.xlsx\"));\n\n            using (var stream = new FileStream(\n                path,\n                FileMode.Open,\n                FileAccess.Read))\n            {\n                using (var ep = new ExcelPackage(stream))\n                {\n                    // get the first worksheet\n\n                    var ws = ep.Workbook.Worksheets[0];\n\n                    // initialize the record counters\n                    var nCountries = 0;\n                    var nCities = 0;\n\n                    #region Import all Countries\n                    // create a list containing all the countries already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCountries = _context.Countries.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n                        var name = row[nRow, 5].GetValue<string>();\n\n                        // does this country already exist in the database?\n                        if (lstCountries.Where(c => c.Name == name).Count() == 0)\n                        {\n                            // create the Country entity and fill it with xlsx data\n                            var country = new Country();\n                            country.Name = name;\n                            country.ISO2 = row[nRow, 6].GetValue<string>();\n                            country.ISO3 = row[nRow, 7].GetValue<string>();\n\n                            // add the new country to the DB context\n                            _context.Countries.Add(country);\n\n                            // store the country to retrieve its Id later on\n                            lstCountries.Add(country);\n\n                            // increment the counter\n                            nCountries++;\n                        }\n                    }\n\n                    // save all the countries into the Database\n                    if (nCountries > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    #region Import all Cities\n                    // create a list containing all the cities already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCities = _context.Cities.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n\n                        var name = row[nRow, 1].GetValue<string>();\n                        var name_ASCII = row[nRow, 2].GetValue<string>();\n                        var countryName = row[nRow, 5].GetValue<string>();\n                        var lat = row[nRow, 3].GetValue<decimal>();\n                        var lon = row[nRow, 4].GetValue<decimal>();\n                        // retrieve country and countryId\n                        var country = lstCountries.Where(c => c.Name == countryName)\n                            .FirstOrDefault();\n                        var countryId = country.Id;\n\n                        // does this city already exist in the database?\n                        if (lstCities.Where(\n                            c => c.Name == name\n                            && c.Lat == lat\n                            && c.Lon == lon\n                            && c.CountryId == countryId\n                        ).Count() == 0)\n                        {\n                            // create the City entity and fill it with xlsx data\n                            var city = new City();\n                            city.Name = name;\n                            city.Name_ASCII = name_ASCII;\n                            city.Lat = lat;\n                            city.Lon = lon;\n                            city.CountryId = countryId;\n\n                            // add the new city to the DB context\n                            _context.Cities.Add(city);\n\n                            // increment the counter\n                            nCities++;\n                        }\n                    }\n\n                    // save all the cities into the Database\n                    if (nCities > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    return new JsonResult(new\n                    {\n                        Cities = nCities,\n                        Countries = nCountries\n                    });\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Chapter_06/WorldCities/Data/ApiResult.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Linq.Dynamic.Core;\nusing System.Reflection;\n\nnamespace WorldCities.Data\n{\n    public class ApiResult<T>\n    {\n        /// <summary>\n        /// Private constructor called by the CreateAsync method.\n        /// </summary>\n        private ApiResult(\n            List<T> data,\n            int count,\n            int pageIndex,\n            int pageSize,\n            string sortColumn,\n            string sortOrder,\n            string filterColumn,\n            string filterQuery)\n        {\n            Data = data;\n            PageIndex = pageIndex;\n            PageSize = pageSize;\n            TotalCount = count;\n            TotalPages = (int)Math.Ceiling(count / (double)pageSize);\n            SortColumn = sortColumn;\n            SortOrder = sortOrder;\n            FilterColumn = filterColumn;\n            FilterQuery = filterQuery;\n        }\n\n        #region Methods\n        /// <summary>\n        /// Pages, sorts and/or filters a IQueryable source.\n        /// </summary>\n        /// <param name=\"source\">An IQueryable source of generic type</param>\n        /// <param name=\"pageIndex\">Zero-based current page index (0 = first page)</param>\n        /// <param name=\"pageSize\">The actual size of each page</param>\n        /// <param name=\"sortColumn\">The sorting colum name</param>\n        /// <param name=\"sortOrder\">The sorting order (\"ASC\" or \"DESC\")</param>\n        /// <param name=\"filterColumn\">The filtering column name</param>\n        /// <param name=\"filterQuery\">The filtering query (value to lookup)</param>\n        /// <returns>\n        /// A object containing the IQueryable paged/sorted/filtered result \n        /// and all the relevant paging/sorting/filtering navigation info.\n        /// </returns>\n        public static async Task<ApiResult<T>> CreateAsync(\n            IQueryable<T> source,\n            int pageIndex,\n            int pageSize,\n            string sortColumn = null,\n            string sortOrder = null,\n            string filterColumn = null,\n            string filterQuery = null)\n        {\n            if (!String.IsNullOrEmpty(filterColumn)\n                && !String.IsNullOrEmpty(filterQuery)\n                && IsValidProperty(filterColumn))\n            {\n                source = source.Where(\n                    String.Format(\"{0}.Contains(@0)\",\n                    filterColumn),\n                    filterQuery);\n            }\n\n            var count = await source.CountAsync();\n\n            if (!String.IsNullOrEmpty(sortColumn)\n                && IsValidProperty(sortColumn))\n            {\n                sortOrder = !String.IsNullOrEmpty(sortOrder)\n                    && sortOrder.ToUpper() == \"ASC\"\n                    ? \"ASC\"\n                    : \"DESC\";\n                source = source.OrderBy(\n                    String.Format(\n                        \"{0} {1}\",\n                        sortColumn,\n                        sortOrder)\n                    );\n            }\n\n            source = source\n                .Skip(pageIndex * pageSize)\n                .Take(pageSize);\n\n            var data = await source.ToListAsync();\n\n            return new ApiResult<T>(\n                data,\n                count,\n                pageIndex,\n                pageSize,\n                sortColumn,\n                sortOrder,\n                filterColumn,\n                filterQuery);\n        }\n\n        /// <summary>\n        /// Checks if the given property name exists\n        /// to protect against SQL injection attacks\n        /// </summary>\n        public static bool IsValidProperty(\n            string propertyName,\n            bool throwExceptionIfNotFound = true)\n        {\n            var prop = typeof(T).GetProperty(\n                propertyName,\n                BindingFlags.IgnoreCase |\n                BindingFlags.Public |\n                BindingFlags.Static |\n                BindingFlags.Instance);\n            if (prop == null && throwExceptionIfNotFound)\n                throw new NotSupportedException(\n                    String.Format(\n                        \"ERROR: Property '{0}' does not exist.\",\n                        propertyName)\n                    );\n            return prop != null;\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// IQueryable data result to return.\n        /// </summary>\n        public List<T> Data { get; private set; }\n\n        /// <summary>\n        /// Zero-based index of current page.\n        /// </summary>\n        public int PageIndex { get; private set; }\n\n        /// <summary>\n        /// Number of items contained in each page.\n        /// </summary>\n        public int PageSize { get; private set; }\n\n        /// <summary>\n        /// Total items count\n        /// </summary>\n        public int TotalCount { get; private set; }\n\n        /// <summary>\n        /// Total pages count\n        /// </summary>\n        public int TotalPages { get; private set; }\n\n        /// <summary>\n        /// TRUE if the current page has a previous page, FALSE otherwise.\n        /// </summary>\n        public bool HasPreviousPage\n        {\n            get\n            {\n                return (PageIndex > 0);\n            }\n        }\n\n        /// <summary>\n        /// TRUE if the current page has a next page, FALSE otherwise.\n        /// </summary>\n        public bool HasNextPage\n        {\n            get\n            {\n                return ((PageIndex +1) < TotalPages);\n            }\n        }\n\n        /// <summary>\n        /// Sorting Column name (or null if none set)\n        /// </summary>\n        public string SortColumn { get; set; }\n\n        /// <summary>\n        /// Sorting Order (\"ASC\", \"DESC\" or null if none set)\n        /// </summary>\n        public string SortOrder { get; set; }\n\n        /// <summary>\n        /// Filter Column name (or null if none set)\n        /// </summary>\n        public string FilterColumn { get; set; }\n\n        /// <summary>\n        /// Filter Query string \n        /// (to be used within the given FilterColumn)\n        /// </summary>\n        public string FilterQuery { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/Data/ApplicationDbContext.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Data\n{\n    public class ApplicationDbContext : DbContext\n    {\n        #region Constructor\n        public ApplicationDbContext() : base()\n        {\n        }\n\n        public ApplicationDbContext(DbContextOptions options) : base(options)\n        {\n        }\n        #endregion Constructor\n\n        #region Methods\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            base.OnModelCreating(modelBuilder);\n\n            // Map Entity names to DB Table names\n            modelBuilder.Entity<City>().ToTable(\"Cities\");\n            modelBuilder.Entity<Country>().ToTable(\"Countries\");\n        }\n        #endregion Methods\n\n        #region Properties\n        public DbSet<City> Cities { get; set; }\n        public DbSet<Country> Countries { get; set; }\n        #endregion Properties\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/Data/Migrations/20191123030140_Initial.Designer.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    [Migration(\"20191123030140_Initial\")]\n    partial class Initial\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/Data/Migrations/20191123030140_Initial.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace WorldCities.Data.Migrations\n{\n    public partial class Initial : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.CreateTable(\n                name: \"Countries\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    ISO2 = table.Column<string>(nullable: true),\n                    ISO3 = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Countries\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"Cities\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    Name_ASCII = table.Column<string>(nullable: true),\n                    Lat = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    Lon = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    CountryId = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Cities\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_Cities_Countries_CountryId\",\n                        column: x => x.CountryId,\n                        principalTable: \"Countries\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_Cities_CountryId\",\n                table: \"Cities\",\n                column: \"CountryId\");\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.DropTable(\n                name: \"Cities\");\n\n            migrationBuilder.DropTable(\n                name: \"Countries\");\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/Data/Migrations/ApplicationDbContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    partial class ApplicationDbContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/Data/Models/City.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class City\n    {\n        #region Constructor\n        public City()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this City\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// City name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// City name (in ASCII format)\n        /// </summary>\n        public string Name_ASCII { get; set; }\n\n        /// <summary>\n        /// City latitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lat { get; set; }\n\n        /// <summary>\n        /// City longitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lon { get; set; }\n\n        /// <summary>\n        /// Country Id (foreign key)\n        /// </summary>\n        [ForeignKey(\"Country\")]\n        public int CountryId { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// The country related to this city.\n        /// </summary>\n        public virtual Country Country { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/Data/Models/Country.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class Country\n    {\n        #region Constructor\n        public Country()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this Country\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// Country name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-2 format)\n        /// </summary>\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-3 format)\n        /// </summary>\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// A list containing all the cities related to this country.\n        /// </summary>\n        public virtual List<City> Cities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_06/WorldCities/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/Pages/_ViewImports.cshtml",
    "content": "@using WorldCities\n@namespace WorldCities.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_06/WorldCities/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing System.Text.Json;\nusing WorldCities.Data;\n\nnamespace WorldCities\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews()\n                .AddJsonOptions(options => {\n                    // set this option to TRUE to indent the JSON output\n                    options.JsonSerializerOptions.WriteIndented = true;\n                    // set this option to NULL to use PascalCase instead of CamelCase (default)\n                    // options.JsonSerializerOptions.PropertyNamingPolicy = null;\n                });\n\n\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            // Add EntityFramework support for SqlServer.\n            services.AddEntityFrameworkSqlServer();\n\n            // Add ApplicationDbContext.\n            services.AddDbContext<ApplicationDbContext>(options =>\n                options.UseSqlServer(\n                    Configuration.GetConnectionString(\"DefaultConnection\")\n                    )\n            );\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles();\n            }\n\n            app.UseRouting();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/WorldCities.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"EPPlus\" Version=\"4.5.3.2\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Debug\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" Version=\"3.1.0\" />\n    <PackageReference Include=\"System.Linq.Dynamic.Core\" Version=\"1.0.20\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_06/WorldCities/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_06/WorldCities/appsettings.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"Server=localhost\\\\SQLEXPRESS;Database=WorldCities;User Id=WorldCities;Password=MyVeryOwn$721;Integrated Security=False;MultipleActiveResultSets=True\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "Chapter_06.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_06\\WorldCities\\WorldCities.csproj\", \"{32CB8454-FCEC-48A0-8A57-064FCF714912}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{32CB8454-FCEC-48A0-8A57-064FCF714912}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{32CB8454-FCEC-48A0-8A57-064FCF714912}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{32CB8454-FCEC-48A0-8A57-064FCF714912}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{32CB8454-FCEC-48A0-8A57-064FCF714912}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_07/WorldCities/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/README.md",
    "content": "# WorldCities\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"WorldCities\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"WorldCities:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"styles.css\"\n            ],\n            \"scripts\": [],\n            \"assets\": [\n              \"src/assets\"\n            ]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\n              \"src/tsconfig.app.json\",\n              \"src/tsconfig.spec.json\"\n            ],\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"WorldCities-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"WorldCities:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"WorldCities\"\n}"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/package.json",
    "content": "{\n  \"name\": \"worldcities\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run WorldCities:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/cdk\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/material\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"hammerjs\": \"2.0.8\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/angular-material.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatPaginatorModule } from '@angular/material/paginator';\nimport { MatSortModule } from '@angular/material/sort';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\n\n@NgModule({\n  imports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ],\n  exports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ]\n})\n\nexport class AngularMaterialModule { }\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/app.component.html",
    "content": "<body>\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { BaseFormComponent } from './base.form.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { CitiesComponent } from './cities/cities.component';\nimport { CityEditComponent } from './cities/city-edit.component';\nimport { CountriesComponent } from './countries/countries.component';\nimport { CountryEditComponent } from './countries/country-edit.component';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { AngularMaterialModule } from './angular-material.module';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    BaseFormComponent,\n    NavMenuComponent,\n    HomeComponent,\n    CitiesComponent,\n    CityEditComponent,\n    CountriesComponent,\n    CountryEditComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    RouterModule.forRoot([\n      { path: '', component: HomeComponent, pathMatch: 'full' },\n      { path: 'cities', component: CitiesComponent },\n      { path: 'city/:id', component: CityEditComponent },\n      { path: 'city', component: CityEditComponent },\n      { path: 'countries', component: CountriesComponent },\n      { path: 'country/:id', component: CountryEditComponent },\n      { path: 'country', component: CountryEditComponent }\n    ]),\n    BrowserAnimationsModule,\n    AngularMaterialModule,\n    ReactiveFormsModule\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/base.form.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { FormGroup } from '@angular/forms';\n\n@Component({\n    template: ''\n})  \nexport class BaseFormComponent {\n\n    // the form model\n    form: FormGroup;\n\n    constructor() {\n    }\n\n    // retrieve a FormControl\n    getControl(name: string) {\n        return this.form.get(name);\n    }\n\n    // returns TRUE if the FormControl is valid\n    isValid(name: string) {\n        var e = this.getControl(name);\n        return e && e.valid;\n    }\n\n    // returns TRUE if the FormControl has been changed\n    isChanged(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched);\n    }\n\n    // returns TRUE if the FormControl is raising an error,\n    // i.e. an invalid state after user changes\n    hasError(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched) && e.invalid;\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/base.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Observable } from 'rxjs';\n\n@Injectable()\nexport abstract class BaseService {\n    constructor(\n        protected http: HttpClient,\n        protected baseUrl: string\n    ) {\n    }\n\n    abstract getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string): Observable<ApiResult>;\n\n    abstract get<T>(id: number): Observable<T>;\n    abstract put<T>(item: T): Observable<T>;\n    abstract post<T>(item: T): Observable<T>;\n}\n\nexport interface ApiResult<T> {\n    data: T[];\n    pageIndex: number;\n    pageSize: number;\n    totalCount: number;\n    totalPages: number;\n    sortColumn: string;\n    sortOrder: string;\n    filterColumn: string;\n    filterQuery: string;\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/cities/cities.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/cities/cities.component.html",
    "content": "<h1>Cities</h1>\n\n<p>Here's a list of cities: feel free to play with it.</p>\n\n<p *ngIf=\"!cities\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"cities\">\n  <button type=\"button\"\n          [routerLink]=\"['/city']\"\n          class=\"btn btn-success\">\n      Add a new City\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!cities\">\n    <input matInput (keyup)=\"loadData($event.target.value)\"\n        placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"cities\" class=\"mat-elevation-z8\" [hidden]=\"!cities\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/city', city.id]\">{{city.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"lat\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Latitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lat}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"lon\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Longitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lon}} </td>\n  </ng-container>\n\n  <!-- CountryName Column -->\n  <ng-container matColumnDef=\"countryName\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Country</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/country', city.countryId]\">{{city.countryName}}</a>\n    </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!cities\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/cities/cities.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { City } from './city';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-cities',\n  templateUrl: './cities.component.html',\n  styleUrls: ['./cities.component.css']\n})\nexport class CitiesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'lat', 'lon', 'countryName'];\n  public cities: MatTableDataSource<City>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery:string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private cityService: CityService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n        this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n      var sortColumn = (this.sort)\n        ? this.sort.active\n        : this.defaultSortColumn;\n\n      var sortOrder = (this.sort)\n        ? this.sort.direction\n        : this.defaultSortOrder;\n\n      var filterColumn = (this.filterQuery)\n        ? this.defaultFilterColumn\n        : null;\n\n      var filterQuery = (this.filterQuery)\n        ? this.filterQuery\n        : null;\n\n      this.cityService.getData<ApiResult<City>>(\n        event.pageIndex,\n        event.pageSize,\n        sortColumn,\n        sortOrder,\n        filterColumn,\n        filterQuery)\n        .subscribe(result => {\n          this.paginator.length = result.totalCount;\n          this.paginator.pageIndex = result.pageIndex;\n          this.paginator.pageSize = result.pageSize;\n          this.cities = new MatTableDataSource<City>(result.data);\n        }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/cities/city-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/cities/city-edit.component.html",
    "content": "<div class=\"city-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !city\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n\n        <div *ngIf=\"form.invalid && form.errors?.isDupeCity\"\n             class=\"alert alert-danger\">\n              <strong>ERROR</strong>:\n              A city with the same <i>name</i>, <i>lat</i>,\n              <i>lon</i> and <i>country</i> already exists.\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"name\">City name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"City name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"hasError('name')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"lat\">City latitude:</label>\n            <br />\n            <input type=\"text\" id=\"lat\"\n                   formControlName=\"lat\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lat')\"\n                  class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lat').errors?.required\">\n                  Latitude is required.\n                </div>\n                <div *ngIf=\"form.get('lat').errors?.pattern\">\n                  Latitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n\n\n        <div class=\"form-group\">\n            <label for=\"lon\">City longitude:</label>\n            <br />\n            <input type=\"text\" id=\"lon\"\n                   formControlName=\"lon\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lon')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lon').errors?.required\">\n                  Longitude is required.\n                </div>\n                <div *ngIf=\"form.get('lon').errors?.pattern\">\n                  Longitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"countryId\">Country:</label>\n            <br />\n            <mat-form-field *ngIf=\"countries\">\n              <mat-label>Select a Country...</mat-label>\n              <mat-select id=\"countryId\" formControlName=\"countryId\">\n                <mat-option *ngFor=\"let country of countries\" [value]=\"country.id\">\n                  {{country.name}}\n                </mat-option>\n              </mat-select>\n            </mat-form-field>\n\n            <div *ngIf=\"hasError('countryId')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('countryId').errors?.required\">\n                  Please select a Country.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/cities']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/cities/city-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormControl, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { City } from './city';\nimport { Country } from '../countries/country';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-city-edit',\n  templateUrl: './city-edit.component.html',\n  styleUrls: ['./city-edit.component.css']\n})\nexport class CityEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  city: City;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new city,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  // the countries array for the select\n  countries: Country[];\n\n  constructor(\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private cityService: CityService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = new FormGroup({\n      name: new FormControl('', Validators.required),\n      lat: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      lon: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      countryId: new FormControl('', Validators.required)\n    }, null, this.isDupeCity());\n\n    this.loadData();\n  }\n\n  loadData() {\n\n    // load countries\n    this.loadCountries();\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the city from the server\n      this.cityService.get<City>(this.id).subscribe(result => {\n        this.city = result;\n        this.title = \"Edit - \" + this.city.name;\n\n        // update the form with the city value\n        this.form.patchValue(this.city);\n      }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new City\";\n    }\n  }\n\n  loadCountries() {\n    // fetch all the countries from the server\n    this.cityService.getCountries<ApiResult<Country>>(\n      0,\n      9999,\n      \"name\",\n      null,\n      null,\n      null,\n      ).subscribe(result => {\n      this.countries = result.data;\n    }, error => console.error(error));\n  }\n\n  onSubmit() {\n\n    var city = (this.id) ? this.city : <City>{};\n\n    city.name = this.form.get(\"name\").value;\n    city.lat = +this.form.get(\"lat\").value;\n    city.lon = +this.form.get(\"lon\").value;\n    city.countryId = +this.form.get(\"countryId\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.cityService\n        .put<City>(city)\n        .subscribe(result => {\n\n            console.log(\"City \" + city.id + \" has been updated.\");\n\n            // go back to cities view\n            this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.cityService\n        .post<City>(city)\n        .subscribe(result => {\n\n            console.log(\"City \" + result.id + \" has been created.\");\n\n            // go back to cities view\n            this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeCity(): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n      var city = <City>{};\n      city.id = (this.id) ? this.id : 0; \n      city.name = this.form.get(\"name\").value;\n      city.lat = +this.form.get(\"lat\").value;\n      city.lon = +this.form.get(\"lon\").value;\n      city.countryId = +this.form.get(\"countryId\").value;\n\n      return this.cityService.isDupeCity(city)\n        .pipe(map(result => {\n            return (result ? { isDupeCity: true } : null);\n        }));\n    }\n  }\n}\n\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/cities/city.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\nimport { City } from './city';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CityService\n  extends BaseService {\n  constructor(\n    http: HttpClient,\n    @Inject('BASE_URL') baseUrl: string) {\n    super(http, baseUrl);\n  }\n\n  getData<ApiResult>(\n    pageIndex: number,\n    pageSize: number,\n    sortColumn: string,\n    sortOrder: string,\n    filterColumn: string,\n    filterQuery: string\n  ): Observable<ApiResult> {\n    var url = this.baseUrl + 'api/Cities';\n    var params = new HttpParams()\n        .set(\"pageIndex\", pageIndex.toString())\n        .set(\"pageSize\", pageSize.toString())\n        .set(\"sortColumn\", sortColumn)\n        .set(\"sortOrder\", sortOrder);\n\n    if (filterQuery) {\n        params = params\n          .set(\"filterColumn\", filterColumn)\n          .set(\"filterQuery\", filterQuery);\n    }\n\n    return this.http.get<ApiResult>(url, { params });\n  }\n\n  get<City>(id): Observable<City> {\n    var url = this.baseUrl + \"api/Cities/\" + id;\n    return this.http.get<City>(url);\n  }\n\n  put<City>(item): Observable<City> {\n    var url = this.baseUrl + \"api/Cities/\" + item.id;\n    return this.http.put<City>(url, item);\n  }\n\n  post<City>(item): Observable<City> {\n    var url = this.baseUrl + \"api/Cities\";\n    return this.http.post<City>(url, item);\n  }\n\n  getCountries<ApiResult>(\n    pageIndex: number,\n    pageSize: number,\n    sortColumn: string,\n    sortOrder: string,\n    filterColumn: string,\n    filterQuery: string\n  ): Observable<ApiResult> {\n    var url = this.baseUrl + 'api/Countries';\n    var params = new HttpParams()\n        .set(\"pageIndex\", pageIndex.toString())\n        .set(\"pageSize\", pageSize.toString())\n        .set(\"sortColumn\", sortColumn)\n        .set(\"sortOrder\", sortOrder);\n\n    if (filterQuery) {\n        params = params\n            .set(\"filterColumn\", filterColumn)\n            .set(\"filterQuery\", filterQuery);\n    }\n\n    return this.http.get<ApiResult>(url, { params });\n  }\n\n  isDupeCity(item): Observable<boolean> {\n    var url = this.baseUrl + \"api/Cities/IsDupeCity\";\n    return this.http.post<boolean>(url, item);\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/cities/city.ts",
    "content": "export interface City {\n    id: number;\n    name: string;\n    lat: number;\n    lon: number;\n    countryId: number;\n    countryName: string;\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/countries/countries.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/countries/countries.component.html",
    "content": "<h1>Countries</h1>\n\n<p>Here's a list of countries: feel free to play with it.</p>\n\n<p *ngIf=\"!countries\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"countries\">\n  <button type=\"button\"\n          [routerLink]=\"['/country']\"\n          class=\"btn btn-success\">\n      Add a new Country\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!countries\">\n  <input matInput (keyup)=\"loadData($event.target.value)\"\n      placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"countries\" class=\"mat-elevation-z8\" [hidden]=\"!countries\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let country\">\n      <a [routerLink]=\"['/country', country.id]\">{{country.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"iso2\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 2</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso2}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"iso3\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 3</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso3}} </td>\n  </ng-container>\n\n  <!-- TotCities Column -->\n  <ng-container matColumnDef=\"totCities\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Tot. Cities</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.totCities}} </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!countries\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/countries/countries.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { Country } from './country';\nimport { CountryService } from './country.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-countries',\n  templateUrl: './countries.component.html',\n  styleUrls: ['./countries.component.css']\n})\nexport class CountriesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'iso2', 'iso3', 'totCities'];\n  public countries: MatTableDataSource<Country>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private countryService: CountryService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n    var sortColumn = (this.sort)\n      ? this.sort.active\n      : this.defaultSortColumn;\n\n    var sortOrder = (this.sort)\n      ? this.sort.direction\n      : this.defaultSortOrder;\n\n    var filterColumn = (this.filterQuery)\n      ? this.defaultFilterColumn\n      : null;\n\n    var filterQuery = (this.filterQuery)\n      ? this.filterQuery\n      : null;\n\n    this.countryService.getData<ApiResult<Country>>(\n      event.pageIndex,\n      event.pageSize,\n      sortColumn,\n      sortOrder,\n      filterColumn,\n      filterQuery)\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.countries = new MatTableDataSource<Country>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/countries/country-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/countries/country-edit.component.html",
    "content": "<div class=\"country-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !country\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n     \n        <div class=\"form-group\">\n            <label for=\"name\">Country name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"Country name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"form.get('name').invalid &&\n                 (form.get('name').dirty || form.get('name').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n                <div *ngIf=\"form.get('name').errors?.isDupeField\">\n                  Name does already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"iso2\">ISO 3166-1 ALPHA-2 Country Code (2 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso2\"\n                   formControlName=\"iso2\" required\n                   placeholder=\"2 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso2').invalid &&\n                 (form.get('iso2').dirty || form.get('iso2').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso2').errors?.required\">\n                  ISO 3166-1 ALPHA-2 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.pattern\">\n                  ISO 3166-1 ALPHA-2 country code requires 2 letters.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-2 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n              <div class=\"form-group\">\n            <label for=\"iso3\">ISO 3166-1 ALPHA-3 Country Code (3 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso3\"\n                   formControlName=\"iso3\" required\n                   placeholder=\"3 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso3').invalid &&\n                 (form.get('iso3').dirty || form.get('iso3').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso3').errors?.required\">\n                  ISO 3166-1 ALPHA-3 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.pattern\">\n                  ISO 3166-1 ALPHA-3 country code requires 3 letters.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-3 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/countries']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/countries/country-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormBuilder, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { map } from 'rxjs/operators';\nimport { Observable } from 'rxjs';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { Country } from '../countries/country';\nimport { CountryService } from './country.service';\n\n@Component({\n  selector: 'app-country-edit',\n  templateUrl: './country-edit.component.html',\n  styleUrls: ['./country-edit.component.css']\n})\nexport class CountryEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  country: Country;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new country,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  constructor(\n    private fb: FormBuilder,\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private countryService: CountryService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = this.fb.group({\n      name: ['',\n        Validators.required,\n        this.isDupeField(\"name\")\n      ],\n      iso2: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{2}/)\n        ],\n        this.isDupeField(\"iso2\")\n      ],\n      iso3: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{3}/)\n        ],\n        this.isDupeField(\"iso3\")\n      ]\n    });\n\n    this.loadData();\n  }\n\n  loadData() {\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the country from the server\n      this.countryService.get<Country>(this.id)\n        .subscribe(result => {\n          this.country = result;\n          this.title = \"Edit - \" + this.country.name;\n\n          // update the form with the country value\n          this.form.patchValue(this.country);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new Country\";\n    }\n  }\n\n  onSubmit() {\n\n    var country = (this.id) ? this.country : <Country>{};\n\n    country.name = this.form.get(\"name\").value;\n    country.iso2 = this.form.get(\"iso2\").value;\n    country.iso3 = this.form.get(\"iso3\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.countryService\n        .put<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + country.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.countryService\n        .post<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeField(fieldName: string): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n\n      var countryId = (this.id) ? this.id.toString() : \"0\";\n\n      return this.countryService.isDupeField(\n        countryId,\n        fieldName,\n        control.value)\n        .pipe(map(result => {\n          return (result ? { isDupeField: true } : null);\n      }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/countries/country.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\nimport { Country } from './country';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CountryService\n  extends BaseService {\n  constructor(\n    http: HttpClient,\n    @Inject('BASE_URL') baseUrl: string) {\n    super(http, baseUrl);\n  }\n\n  getData<ApiResult>(\n    pageIndex: number,\n    pageSize: number,\n    sortColumn: string,\n    sortOrder: string,\n    filterColumn: string,\n    filterQuery: string\n  ): Observable<ApiResult> {\n    var url = this.baseUrl + 'api/Countries';\n    var params = new HttpParams()\n      .set(\"pageIndex\", pageIndex.toString())\n      .set(\"pageSize\", pageSize.toString())\n      .set(\"sortColumn\", sortColumn)\n      .set(\"sortOrder\", sortOrder);\n\n    if (filterQuery) {\n      params = params\n      .set(\"filterColumn\", filterColumn)\n      .set(\"filterQuery\", filterQuery);\n    }\n\n      return this.http.get<ApiResult>(url, { params });\n  }\n\n  get<Country>(id): Observable<Country> {\n    var url = this.baseUrl + \"api/Countries/\" + id;\n    return this.http.get<Country>(url);\n  }\n\n  put<Country>(item): Observable<Country> {\n    var url = this.baseUrl + \"api/Countries/\" + item.id;\n    return this.http.put<Country>(url, item);\n  }\n\n  post<Country>(item): Observable<Country> {\n    var url = this.baseUrl + \"api/Countries\";\n    return this.http.post<Country>(url, item);\n  }\n\n  isDupeField(countryId, fieldName, fieldValue): Observable<boolean> {\n    var params = new HttpParams()\n      .set(\"countryId\", countryId)\n      .set(\"fieldName\", fieldName)\n      .set(\"fieldValue\", fieldValue);\n    var url = this.baseUrl + \"api/Countries/IsDupeField\";\n    return this.http.post<boolean>(url, null, { params });\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/countries/country.ts",
    "content": "export interface Country {\n    id: number;\n    name: string;\n    iso2: string;\n    iso3: string;\n    totCities: number;\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">WorldCities</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/cities']\"\n              >Cities</a\n            >\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/countries']\"\n              >Countries</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>WorldCities</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n      <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n</head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/main.ts",
    "content": "import 'hammerjs';\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_07/WorldCities/Controllers/CitiesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CitiesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CitiesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Cities/?pageIndex=0&pageSize=10\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CityDTO>>> GetCities(\n                int pageIndex = 0,\n                int pageSize = 10,\n                string sortColumn = null,\n                string sortOrder = null,\n                string filterColumn = null,\n                string filterQuery = null)\n        {\n            return await ApiResult<CityDTO>.CreateAsync(\n                    _context.Cities\n                        .Select(c => new CityDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            Lat = c.Lat,\n                            Lon = c.Lon,\n                            CountryId = c.Country.Id,\n                            CountryName = c.Country.Name\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        // GET: api/Cities/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<City>> GetCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            return city;\n        }\n\n        // PUT: api/Cities/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCity(int id, City city)\n        {\n            if (id != city.Id)\n            {\n                return BadRequest();\n            }\n\n            //var sourceCity = _context.Cities.Where(i => i.Id == city.Id).FirstOrDefault();\n            //if (sourceCity == null) return BadRequest();\n            //sourceCity.Name = city.Name;\n            //sourceCity.Lat = city.Lat;\n            //sourceCity.Lon = city.Lon;\n\n            _context.Entry(city).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CityExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Cities\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<City>> PostCity(City city)\n        {\n            _context.Cities.Add(city);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCity\", new { id = city.Id }, city);\n        }\n\n        // DELETE: api/Cities/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<City>> DeleteCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            _context.Cities.Remove(city);\n            await _context.SaveChangesAsync();\n\n            return city;\n        }\n\n        private bool CityExists(int id)\n        {\n            return _context.Cities.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeCity\")]\n        public bool IsDupeCity(City city)\n        {\n            return _context.Cities.Any(\n                e => e.Name == city.Name\n                && e.Lat == city.Lat\n                && e.Lon == city.Lon\n                && e.CountryId == city.CountryId\n                && e.Id != city.Id);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Controllers/CountriesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Dynamic.Core;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CountriesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CountriesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Countries/?pageIndex=0&pageSize=10\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CountryDTO>>> GetCountries(\n        int pageIndex = 0,\n        int pageSize = 10,\n        string sortColumn = null,\n        string sortOrder = null,\n        string filterColumn = null,\n        string filterQuery = null)\n        {\n            return await ApiResult<CountryDTO>.CreateAsync(\n                    _context.Countries\n                        .Select(c => new CountryDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            ISO2 = c.ISO2,\n                            ISO3 = c.ISO3,\n                            TotCities = c.Cities.Count\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        //public async Task<ActionResult<ApiResult<dynamic>>> GetCountries(\n        //    int pageIndex = 0,\n        //    int pageSize = 10,\n        //    string sortColumn = null,\n        //    string sortOrder = null,\n        //    string filterColumn = null,\n        //    string filterQuery = null)\n        //{\n        //    return await ApiResult<dynamic>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n\n        //public async Task<ActionResult<ApiResult<Country>>> GetCountries(\n        //int pageIndex = 0,\n        //int pageSize = 10,\n        //string sortColumn = null,\n        //string sortOrder = null,\n        //string filterColumn = null,\n        //string filterQuery = null)\n        //{\n        //    return await ApiResult<Country>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new Country()\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n        // GET: api/Countries/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<Country>> GetCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            return country;\n        }\n\n        // PUT: api/Countries/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCountry(int id, Country country)\n        {\n            if (id != country.Id)\n            {\n                return BadRequest();\n            }\n\n            _context.Entry(country).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CountryExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Countries\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<Country>> PostCountry(Country country)\n        {\n            _context.Countries.Add(country);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCountry\", new { id = country.Id }, country);\n        }\n\n        // DELETE: api/Countries/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<Country>> DeleteCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            _context.Countries.Remove(country);\n            await _context.SaveChangesAsync();\n\n            return country;\n        }\n\n        private bool CountryExists(int id)\n        {\n            return _context.Countries.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeField\")]\n        public bool IsDupeField(\n            int countryId, \n            string fieldName, \n            string fieldValue)\n        {\n            // Standard approach(using strongly-typed LAMBA expressions)\n            //switch (fieldName)\n            //{\n            //    case \"name\":\n            //        return _context.Countries.Any(\n            //            c => c.Name == fieldValue && c.Id != countryId);\n            //    case \"iso2\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO2 == fieldValue && c.Id != countryId);\n            //    case \"iso3\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO3 == fieldValue && c.Id != countryId);\n            //    default:\n            //        return false;\n            //}\n\n            // Dynamic approach (using System.Linq.Dynamic.Core)\n            return (ApiResult<Country>.IsValidProperty(fieldName, true))\n                ? _context.Countries.Any(\n                    String.Format(\"{0} == @0 && Id != @1\", fieldName),\n                    fieldValue,\n                    countryId)\n                : false;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Controllers/SeedController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing WorldCities.Data;\nusing OfficeOpenXml;\nusing System.IO;\nusing Microsoft.AspNetCore.Hosting;\nusing WorldCities.Data.Models;\nusing System.Text.Json;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]/[action]\")]\n    [ApiController]\n    public class SeedController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n        private readonly IWebHostEnvironment _env;\n\n        public SeedController(\n            ApplicationDbContext context, \n            IWebHostEnvironment env)\n        {\n            _context = context;\n            _env = env;\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> Import()\n        {\n            // NOTE: This method has been updated on 2020.09.13.\n            // The new version is more efficient than the code described in the book's Chapter 4.\n            // ref.: https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/issues/15\n\n            var path = Path.Combine(\n                _env.ContentRootPath,\n                String.Format(\"Data/Source/worldcities.xlsx\"));\n\n            using (var stream = new FileStream(\n                path,\n                FileMode.Open,\n                FileAccess.Read))\n            {\n                using (var ep = new ExcelPackage(stream))\n                {\n                    // get the first worksheet\n\n                    var ws = ep.Workbook.Worksheets[0];\n\n                    // initialize the record counters\n                    var nCountries = 0;\n                    var nCities = 0;\n\n                    #region Import all Countries\n                    // create a list containing all the countries already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCountries = _context.Countries.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n                        var name = row[nRow, 5].GetValue<string>();\n\n                        // does this country already exist in the database?\n                        if (lstCountries.Where(c => c.Name == name).Count() == 0)\n                        {\n                            // create the Country entity and fill it with xlsx data\n                            var country = new Country();\n                            country.Name = name;\n                            country.ISO2 = row[nRow, 6].GetValue<string>();\n                            country.ISO3 = row[nRow, 7].GetValue<string>();\n\n                            // add the new country to the DB context\n                            _context.Countries.Add(country);\n\n                            // store the country to retrieve its Id later on\n                            lstCountries.Add(country);\n\n                            // increment the counter\n                            nCountries++;\n                        }\n                    }\n\n                    // save all the countries into the Database\n                    if (nCountries > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    #region Import all Cities\n                    // create a list containing all the cities already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCities = _context.Cities.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n\n                        var name = row[nRow, 1].GetValue<string>();\n                        var name_ASCII = row[nRow, 2].GetValue<string>();\n                        var countryName = row[nRow, 5].GetValue<string>();\n                        var lat = row[nRow, 3].GetValue<decimal>();\n                        var lon = row[nRow, 4].GetValue<decimal>();\n                        // retrieve country and countryId\n                        var country = lstCountries.Where(c => c.Name == countryName)\n                            .FirstOrDefault();\n                        var countryId = country.Id;\n\n                        // does this city already exist in the database?\n                        if (lstCities.Where(\n                            c => c.Name == name\n                            && c.Lat == lat\n                            && c.Lon == lon\n                            && c.CountryId == countryId\n                        ).Count() == 0)\n                        {\n                            // create the City entity and fill it with xlsx data\n                            var city = new City();\n                            city.Name = name;\n                            city.Name_ASCII = name_ASCII;\n                            city.Lat = lat;\n                            city.Lon = lon;\n                            city.CountryId = countryId;\n\n                            // add the new city to the DB context\n                            _context.Cities.Add(city);\n\n                            // increment the counter\n                            nCities++;\n                        }\n                    }\n\n                    // save all the cities into the Database\n                    if (nCities > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    return new JsonResult(new\n                    {\n                        Cities = nCities,\n                        Countries = nCountries\n                    });\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Chapter_07/WorldCities/Data/ApiResult.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Linq.Dynamic.Core;\nusing System.Reflection;\nusing System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class ApiResult<T>\n    {\n        /// <summary>\n        /// Private constructor called by the CreateAsync method.\n        /// </summary>\n        private ApiResult(\n            List<T> data,\n            int count,\n            int pageIndex,\n            int pageSize,\n            string sortColumn,\n            string sortOrder,\n            string filterColumn,\n            string filterQuery)\n        {\n            Data = data;\n            PageIndex = pageIndex;\n            PageSize = pageSize;\n            TotalCount = count;\n            TotalPages = (int)Math.Ceiling(count / (double)pageSize);\n            SortColumn = sortColumn;\n            SortOrder = sortOrder;\n            FilterColumn = filterColumn;\n            FilterQuery = filterQuery;\n        }\n\n        #region Methods\n        /// <summary>\n        /// Pages, sorts and/or filters a IQueryable source.\n        /// </summary>\n        /// <param name=\"source\">An IQueryable source of generic type</param>\n        /// <param name=\"pageIndex\">Zero-based current page index (0 = first page)</param>\n        /// <param name=\"pageSize\">The actual size of each page</param>\n        /// <param name=\"sortColumn\">The sorting colum name</param>\n        /// <param name=\"sortOrder\">The sorting order (\"ASC\" or \"DESC\")</param>\n        /// <param name=\"filterColumn\">The filtering column name</param>\n        /// <param name=\"filterQuery\">The filtering query (value to lookup)</param>\n        /// <returns>\n        /// A object containing the IQueryable paged/sorted/filtered result \n        /// and all the relevant paging/sorting/filtering navigation info.\n        /// </returns>\n        public static async Task<ApiResult<T>> CreateAsync(\n            IQueryable<T> source,\n            int pageIndex,\n            int pageSize,\n            string sortColumn = null,\n            string sortOrder = null,\n            string filterColumn = null,\n            string filterQuery = null)\n        {\n            if (!String.IsNullOrEmpty(filterColumn)\n                && !String.IsNullOrEmpty(filterQuery)\n                && IsValidProperty(filterColumn))\n            {\n                source = source.Where(\n                    String.Format(\"{0}.Contains(@0)\",\n                    filterColumn),\n                    filterQuery);\n            }\n\n            var count = await source.CountAsync();\n\n            if (!String.IsNullOrEmpty(sortColumn)\n                && IsValidProperty(sortColumn))\n            {\n                sortOrder = !String.IsNullOrEmpty(sortOrder)\n                    && sortOrder.ToUpper() == \"ASC\"\n                    ? \"ASC\"\n                    : \"DESC\";\n                source = source.OrderBy(\n                    String.Format(\n                        \"{0} {1}\",\n                        sortColumn,\n                        sortOrder)\n                    );\n            }\n\n            source = source\n                .Skip(pageIndex * pageSize)\n                .Take(pageSize);\n\n            var data = await source.ToListAsync();\n\n            return new ApiResult<T>(\n                data,\n                count,\n                pageIndex,\n                pageSize,\n                sortColumn,\n                sortOrder,\n                filterColumn,\n                filterQuery);\n        }\n\n        /// <summary>\n        /// Checks if the given property name exists\n        /// to protect against SQL injection attacks\n        /// </summary>\n        public static bool IsValidProperty(\n            string propertyName,\n            bool throwExceptionIfNotFound = true)\n        {\n            var prop = typeof(T).GetProperty(\n                propertyName,\n                BindingFlags.IgnoreCase |\n                BindingFlags.Public |\n                BindingFlags.Static |\n                BindingFlags.Instance);\n            if (prop == null && throwExceptionIfNotFound)\n                throw new NotSupportedException(\n                    String.Format(\n                        \"ERROR: Property '{0}' does not exist.\",\n                        propertyName)\n                    );\n            return prop != null;\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The data result.\n        /// </summary>\n        public List<T> Data { get; private set; }\n\n        /// <summary>\n        /// Zero-based index of current page.\n        /// </summary>\n        public int PageIndex { get; private set; }\n\n        /// <summary>\n        /// Number of items contained in each page.\n        /// </summary>\n        public int PageSize { get; private set; }\n\n        /// <summary>\n        /// Total items count\n        /// </summary>\n        public int TotalCount { get; private set; }\n\n        /// <summary>\n        /// Total pages count\n        /// </summary>\n        public int TotalPages { get; private set; }\n\n        /// <summary>\n        /// TRUE if the current page has a previous page, FALSE otherwise.\n        /// </summary>\n        public bool HasPreviousPage\n        {\n            get\n            {\n                return (PageIndex > 0);\n            }\n        }\n\n        /// <summary>\n        /// TRUE if the current page has a next page, FALSE otherwise.\n        /// </summary>\n        public bool HasNextPage\n        {\n            get\n            {\n                return ((PageIndex +1) < TotalPages);\n            }\n        }\n\n        /// <summary>\n        /// Sorting Column name (or null if none set)\n        /// </summary>\n        public string SortColumn { get; set; }\n\n        /// <summary>\n        /// Sorting Order (\"ASC\", \"DESC\" or null if none set)\n        /// </summary>\n        public string SortOrder { get; set; }\n\n        /// <summary>\n        /// Filter Column name (or null if none set)\n        /// </summary>\n        public string FilterColumn { get; set; }\n\n        /// <summary>\n        /// Filter Query string \n        /// (to be used within the given FilterColumn)\n        /// </summary>\n        public string FilterQuery { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Data/ApplicationDbContext.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Data\n{\n    public class ApplicationDbContext : DbContext\n    {\n        #region Constructor\n        public ApplicationDbContext() : base()\n        {\n        }\n\n        public ApplicationDbContext(DbContextOptions options) : base(options)\n        {\n        }\n        #endregion Constructor\n\n        #region Methods\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            base.OnModelCreating(modelBuilder);\n\n            // Map Entity names to DB Table names\n            modelBuilder.Entity<City>().ToTable(\"Cities\");\n            modelBuilder.Entity<Country>().ToTable(\"Countries\");\n        }\n        #endregion Methods\n\n        #region Properties\n        public DbSet<City> Cities { get; set; }\n        public DbSet<Country> Countries { get; set; }\n        #endregion Properties\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Data/CityDTO.cs",
    "content": "﻿namespace WorldCities.Data\n{\n    public class CityDTO\n    {\n        public CityDTO() { }\n\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        public string Name_ASCII { get; set; }\n\n        public decimal Lat { get; set; }\n\n        public decimal Lon { get; set; }\n\n        public int CountryId { get; set; }\n\n        public string CountryName { get; set; }\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Data/CountryDTO.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class CountryDTO\n    {\n        public CountryDTO() { }\n\n        #region Properties\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n\n        public int TotCities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Data/Migrations/20191123030140_Initial.Designer.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    [Migration(\"20191123030140_Initial\")]\n    partial class Initial\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Data/Migrations/20191123030140_Initial.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace WorldCities.Data.Migrations\n{\n    public partial class Initial : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.CreateTable(\n                name: \"Countries\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    ISO2 = table.Column<string>(nullable: true),\n                    ISO3 = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Countries\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"Cities\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    Name_ASCII = table.Column<string>(nullable: true),\n                    Lat = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    Lon = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    CountryId = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Cities\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_Cities_Countries_CountryId\",\n                        column: x => x.CountryId,\n                        principalTable: \"Countries\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_Cities_CountryId\",\n                table: \"Cities\",\n                column: \"CountryId\");\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.DropTable(\n                name: \"Cities\");\n\n            migrationBuilder.DropTable(\n                name: \"Countries\");\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Data/Migrations/ApplicationDbContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    partial class ApplicationDbContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Data/Models/City.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class City\n    {\n        #region Constructor\n        public City()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this City\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// City name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// City name (in ASCII format)\n        /// </summary>\n        public string Name_ASCII { get; set; }\n\n        /// <summary>\n        /// City latitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lat { get; set; }\n\n        /// <summary>\n        /// City longitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lon { get; set; }\n\n        /// <summary>\n        /// Country Id (foreign key)\n        /// </summary>\n        [ForeignKey(\"Country\")]\n        public int CountryId { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// The country related to this city.\n        /// </summary>\n        public virtual Country Country { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Data/Models/Country.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class Country\n    {\n        #region Constructor\n        public Country()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this Country\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// Country name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-2 format)\n        /// </summary>\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-3 format)\n        /// </summary>\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n        #endregion\n\n        #region Client-side properties\n        /// <summary>\n        /// The number of cities related to this country.\n        /// </summary>\n        [NotMapped]\n        public int TotCities\n        {\n            get\n            {\n                return (Cities != null)\n                    ? Cities.Count\n                    : _TotCities;\n            }\n            set { _TotCities = value; }\n        }\n\n        private int _TotCities = 0;\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// A list containing all the cities related to this country.\n        /// </summary>\n        [JsonIgnore]\n        public virtual List<City> Cities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_07/WorldCities/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Pages/_ViewImports.cshtml",
    "content": "@using WorldCities\n@namespace WorldCities.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_07/WorldCities/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing System.Text.Json;\nusing WorldCities.Data;\n\nnamespace WorldCities\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews()\n                .AddJsonOptions(options => {\n                    // set this option to TRUE to indent the JSON output\n                    options.JsonSerializerOptions.WriteIndented = true;\n                    // set this option to NULL to use PascalCase instead of CamelCase (default)\n                    // options.JsonSerializerOptions.PropertyNamingPolicy = null;\n                });\n\n\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            // Add EntityFramework support for SqlServer.\n            services.AddEntityFrameworkSqlServer();\n\n            // Add ApplicationDbContext.\n            services.AddDbContext<ApplicationDbContext>(options =>\n                options.UseSqlServer(\n                    Configuration.GetConnectionString(\"DefaultConnection\")\n                    )\n            );\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles();\n            }\n\n            app.UseRouting();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/WorldCities.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"EPPlus\" Version=\"4.5.3.2\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Debug\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" Version=\"3.1.0\" />\n    <PackageReference Include=\"System.Linq.Dynamic.Core\" Version=\"1.0.20\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_07/WorldCities/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_07/WorldCities/appsettings.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"Server=localhost\\\\SQLEXPRESS;Database=WorldCities;User Id=WorldCities;Password=MyVeryOwn$721;Integrated Security=False;MultipleActiveResultSets=True\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "Chapter_07.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_07\\WorldCities\\WorldCities.csproj\", \"{E0BC7DE4-EA66-485B-809F-734359B7FC78}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{E0BC7DE4-EA66-485B-809F-734359B7FC78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{E0BC7DE4-EA66-485B-809F-734359B7FC78}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{E0BC7DE4-EA66-485B-809F-734359B7FC78}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{E0BC7DE4-EA66-485B-809F-734359B7FC78}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_08/WorldCities/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/README.md",
    "content": "# WorldCities\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"WorldCities\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"WorldCities:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"styles.css\"\n            ],\n            \"scripts\": [],\n            \"assets\": [\n              \"src/assets\"\n            ]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\n              \"src/tsconfig.app.json\",\n              \"src/tsconfig.spec.json\"\n            ],\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"WorldCities-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"WorldCities:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"WorldCities\"\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/package.json",
    "content": "{\n  \"name\": \"worldcities\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run WorldCities:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/cdk\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/material\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"hammerjs\": \"2.0.8\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/angular-material.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatPaginatorModule } from '@angular/material/paginator';\nimport { MatSortModule } from '@angular/material/sort';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\n\n@NgModule({\n  imports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ],\n  exports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ]\n})\n\nexport class AngularMaterialModule { }\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/app.component.html",
    "content": "<body>\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { BaseFormComponent } from './base.form.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { CitiesComponent } from './cities/cities.component';\nimport { CityEditComponent } from './cities/city-edit.component';\nimport { CountriesComponent } from './countries/countries.component';\nimport { CountryEditComponent } from './countries/country-edit.component';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { AngularMaterialModule } from './angular-material.module';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    BaseFormComponent,\n    NavMenuComponent,\n    HomeComponent,\n    CitiesComponent,\n    CityEditComponent,\n    CountriesComponent,\n    CountryEditComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    RouterModule.forRoot([\n      { path: '', component: HomeComponent, pathMatch: 'full' },\n      { path: 'cities', component: CitiesComponent },\n      { path: 'city/:id', component: CityEditComponent },\n      { path: 'city', component: CityEditComponent },\n      { path: 'countries', component: CountriesComponent },\n      { path: 'country/:id', component: CountryEditComponent },\n      { path: 'country', component: CountryEditComponent }\n    ]),\n    BrowserAnimationsModule,\n    AngularMaterialModule,\n    ReactiveFormsModule\n    ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/base.form.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { FormGroup } from '@angular/forms';\n\n@Component({\n    template: ''\n})  \nexport class BaseFormComponent {\n\n    // the form model\n    form: FormGroup;\n\n    constructor() {\n    }\n\n    // retrieve a FormControl\n    getControl(name: string) {\n        return this.form.get(name);\n    }\n\n    // returns TRUE if the FormControl is valid\n    isValid(name: string) {\n        var e = this.getControl(name);\n        return e && e.valid;\n    }\n\n    // returns TRUE if the FormControl has been changed\n    isChanged(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched);\n    }\n\n    // returns TRUE if the FormControl is raising an error,\n    // i.e. an invalid state after user changes\n    hasError(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched) && e.invalid;\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/base.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Observable } from 'rxjs';\n\n@Injectable()\nexport abstract class BaseService {\n    constructor(\n        protected http: HttpClient,\n        protected baseUrl: string\n    ) {\n    }\n\n    abstract getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string): Observable<ApiResult>;\n\n    abstract get<T>(id: number): Observable<T>;\n    abstract put<T>(item: T): Observable<T>;\n    abstract post<T>(item: T): Observable<T>;\n}\n\nexport interface ApiResult<T> {\n    data: T[];\n    pageIndex: number;\n    pageSize: number;\n    totalCount: number;\n    totalPages: number;\n    sortColumn: string;\n    sortOrder: string;\n    filterColumn: string;\n    filterQuery: string;\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/cities/cities.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/cities/cities.component.html",
    "content": "<h1>Cities</h1>\n\n<p>Here's a list of cities: feel free to play with it.</p>\n\n<p *ngIf=\"!cities\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"cities\">\n  <button type=\"button\"\n          [routerLink]=\"['/city']\"\n          class=\"btn btn-success\">\n      Add a new City\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!cities\">\n    <input matInput (keyup)=\"loadData($event.target.value)\"\n        placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"cities\" class=\"mat-elevation-z8\" [hidden]=\"!cities\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/city', city.id]\">{{city.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"lat\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Latitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lat}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"lon\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Longitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lon}} </td>\n  </ng-container>\n\n  <!-- CountryName Column -->\n  <ng-container matColumnDef=\"countryName\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Country</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/country', city.countryId]\">{{city.countryName}}</a>\n    </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!cities\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/cities/cities.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { City } from './city';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-cities',\n  templateUrl: './cities.component.html',\n  styleUrls: ['./cities.component.css']\n})\nexport class CitiesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'lat', 'lon', 'countryName'];\n  public cities: MatTableDataSource<City>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery:string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private cityService: CityService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n        this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n      var sortColumn = (this.sort)\n        ? this.sort.active\n        : this.defaultSortColumn;\n\n      var sortOrder = (this.sort)\n        ? this.sort.direction\n        : this.defaultSortOrder;\n\n      var filterColumn = (this.filterQuery)\n        ? this.defaultFilterColumn\n        : null;\n\n      var filterQuery = (this.filterQuery)\n        ? this.filterQuery\n        : null;\n\n      this.cityService.getData<ApiResult<City>>(\n        event.pageIndex,\n        event.pageSize,\n        sortColumn,\n        sortOrder,\n        filterColumn,\n        filterQuery)\n        .subscribe(result => {\n          this.paginator.length = result.totalCount;\n          this.paginator.pageIndex = result.pageIndex;\n          this.paginator.pageSize = result.pageSize;\n          this.cities = new MatTableDataSource<City>(result.data);\n        }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/cities/city-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/cities/city-edit.component.html",
    "content": "<div class=\"city-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !city\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n\n        <div *ngIf=\"form.invalid && form.errors?.isDupeCity\"\n             class=\"alert alert-danger\">\n              <strong>ERROR</strong>:\n              A city with the same <i>name</i>, <i>lat</i>,\n              <i>lon</i> and <i>country</i> already exists.\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"name\">City name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"City name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"hasError('name')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"lat\">City latitude:</label>\n            <br />\n            <input type=\"text\" id=\"lat\"\n                   formControlName=\"lat\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lat')\"\n                  class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lat').errors?.required\">\n                  Latitude is required.\n                </div>\n                <div *ngIf=\"form.get('lat').errors?.pattern\">\n                  Latitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n\n\n        <div class=\"form-group\">\n            <label for=\"lon\">City longitude:</label>\n            <br />\n            <input type=\"text\" id=\"lon\"\n                   formControlName=\"lon\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lon')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lon').errors?.required\">\n                  Longitude is required.\n                </div>\n                <div *ngIf=\"form.get('lon').errors?.pattern\">\n                  Longitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"countryId\">Country:</label>\n            <br />\n            <mat-form-field *ngIf=\"countries\">\n              <mat-label>Select a Country...</mat-label>\n              <mat-select id=\"countryId\" formControlName=\"countryId\">\n                <mat-option *ngFor=\"let country of countries\" [value]=\"country.id\">\n                  {{country.name}}\n                </mat-option>\n              </mat-select>\n            </mat-form-field>\n\n            <div *ngIf=\"hasError('countryId')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('countryId').errors?.required\">\n                  Please select a Country.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/cities']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n\n<!-- Form debug info panel -->\n<div class=\"card bg-light mb-3\">\n  <div class=\"card-header\">Form Debug Info</div>\n  <div class=\"card-body\">\n    <div class=\"card-text\">\n      <div><strong>Form value:</strong></div>\n      <div class=\"help-block\">\n          {{ form.value | json }}\n      </div>\n      <div class=\"mt-2\"><strong>Form status:</strong></div>\n      <div class=\"help-block\">\n          {{ form.status | json }}\n      </div>\n    </div>\n  </div>\n</div>\n\n<!-- Form activity log panel -->\n<div class=\"card bg-light mb-3\">\n  <div class=\"card-header\">Form Activity Log</div>\n  <div class=\"card-body\">\n    <div class=\"card-text\">\n      <div class=\"help-block\">\n        <span *ngIf=\"activityLog\" \n            [innerHTML]=\"activityLog\"></span>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/cities/city-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormControl, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { City } from './city';\nimport { Country } from '../countries/country';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-city-edit',\n  templateUrl: './city-edit.component.html',\n  styleUrls: ['./city-edit.component.css']\n})\nexport class CityEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  city: City;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new city,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  // the countries array for the select\n  countries: Country[];\n\n  // Activity Log (for debugging purposes)\n  activityLog: string = '';\n\n  constructor(\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private cityService: CityService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = new FormGroup({\n      name: new FormControl('', Validators.required),\n      lat: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      lon: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      countryId: new FormControl('', Validators.required)\n    }, null, this.isDupeCity());\n\n    // react to form changes\n    this.form.valueChanges\n      .subscribe(val => {\n        if (!this.form.dirty) {\n          this.log(\"Form Model has been loaded.\");\n        }\n        else {\n          this.log(\"Form was updated by the user.\");\n        }\n      });\n\n    // react to changes in the form.name control\n    this.form.get(\"name\")!.valueChanges\n      .subscribe(val => {\n        if (!this.form.dirty) {\n          this.log(\"Name has been loaded with initial values.\");\n        }\n        else {\n          this.log(\"Name was updated by the user.\");\n        }\n      });\n\n    this.loadData();\n  }\n\n  log(str: string) {\n    this.activityLog += \"[\"\n      + new Date().toLocaleString()\n      + \"] \" + str + \"<br />\";\n  }\n\n  loadData() {\n\n    // load countries\n    this.loadCountries();\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the city from the server\n      this.cityService.get<City>(this.id).subscribe(result => {\n        this.city = result;\n        this.title = \"Edit - \" + this.city.name;\n\n        // update the form with the city value\n        this.form.patchValue(this.city);\n      }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new City\";\n    }\n  }\n\n  loadCountries() {\n    // fetch all the countries from the server\n    this.cityService.getCountries<ApiResult<Country>>(\n      0,\n      9999,\n      \"name\",\n      null,\n      null,\n      null,\n    ).subscribe(result => {\n      this.countries = result.data;\n    }, error => console.error(error));\n  }\n\n  onSubmit() {\n\n    var city = (this.id) ? this.city : <City>{};\n\n    city.name = this.form.get(\"name\").value;\n    city.lat = +this.form.get(\"lat\").value;\n    city.lon = +this.form.get(\"lon\").value;\n    city.countryId = +this.form.get(\"countryId\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.cityService\n        .put<City>(city)\n        .subscribe(result => {\n\n          console.log(\"City \" + city.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.cityService\n        .post<City>(city)\n        .subscribe(result => {\n\n          console.log(\"City \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeCity(): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n      var city = <City>{};\n      city.id = (this.id) ? this.id : 0;\n      city.name = this.form.get(\"name\").value;\n      city.lat = +this.form.get(\"lat\").value;\n      city.lon = +this.form.get(\"lon\").value;\n      city.countryId = +this.form.get(\"countryId\").value;\n\n      return this.cityService.isDupeCity(city)\n        .pipe(map(result => {\n          return (result ? { isDupeCity: true } : null);\n        }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/cities/city.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CityService\n    extends BaseService {\n    constructor(\n        http: HttpClient,\n        @Inject('BASE_URL') baseUrl: string) {\n        super(http, baseUrl);\n    }\n\n    getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Cities';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    get<City>(id): Observable<City> {\n        var url = this.baseUrl + \"api/Cities/\" + id;\n        return this.http.get<City>(url);\n    }\n\n    put<City>(item): Observable<City> {\n        var url = this.baseUrl + \"api/Cities/\" + item.id;\n        return this.http.put<City>(url, item);\n    }\n\n    post<City>(item): Observable<City> {\n        var url = this.baseUrl + \"api/Cities\";\n        return this.http.post<City>(url, item);\n    }\n\n    getCountries<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Countries';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    isDupeCity(item): Observable<boolean> {\n        var url = this.baseUrl + \"api/Cities/IsDupeCity\";\n        return this.http.post<boolean>(url, item);\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/cities/city.ts",
    "content": "export interface City {\n    id: number;\n    name: string;\n    lat: number;\n    lon: number;\n    countryId: number;\n    countryName: string;\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/countries/countries.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/countries/countries.component.html",
    "content": "<h1>Countries</h1>\n\n<p>Here's a list of countries: feel free to play with it.</p>\n\n<p *ngIf=\"!countries\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"countries\">\n  <button type=\"button\"\n          [routerLink]=\"['/country']\"\n          class=\"btn btn-success\">\n      Add a new Country\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!countries\">\n  <input matInput (keyup)=\"loadData($event.target.value)\"\n      placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"countries\" class=\"mat-elevation-z8\" [hidden]=\"!countries\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let country\">\n      <a [routerLink]=\"['/country', country.id]\">{{country.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"iso2\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 2</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso2}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"iso3\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 3</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso3}} </td>\n  </ng-container>\n\n  <!-- TotCities Column -->\n  <ng-container matColumnDef=\"totCities\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Tot. Cities</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.totCities}} </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!countries\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/countries/countries.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { Country } from './country';\nimport { CountryService } from './country.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-countries',\n  templateUrl: './countries.component.html',\n  styleUrls: ['./countries.component.css']\n})\nexport class CountriesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'iso2', 'iso3', 'totCities'];\n  public countries: MatTableDataSource<Country>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private countryService: CountryService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n    var sortColumn = (this.sort)\n      ? this.sort.active\n      : this.defaultSortColumn;\n\n    var sortOrder = (this.sort)\n      ? this.sort.direction\n      : this.defaultSortOrder;\n\n    var filterColumn = (this.filterQuery)\n      ? this.defaultFilterColumn\n      : null;\n\n    var filterQuery = (this.filterQuery)\n      ? this.filterQuery\n      : null;\n\n    this.countryService.getData<ApiResult<Country>>(\n      event.pageIndex,\n      event.pageSize,\n      sortColumn,\n      sortOrder,\n      filterColumn,\n      filterQuery)\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.countries = new MatTableDataSource<Country>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/countries/country-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/countries/country-edit.component.html",
    "content": "<div class=\"country-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !country\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n     \n        <div class=\"form-group\">\n            <label for=\"name\">Country name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"Country name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"form.get('name').invalid &&\n                 (form.get('name').dirty || form.get('name').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n                <div *ngIf=\"form.get('name').errors?.isDupeField\">\n                  Name does already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"iso2\">ISO 3166-1 ALPHA-2 Country Code (2 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso2\"\n                   formControlName=\"iso2\" required\n                   placeholder=\"2 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso2').invalid &&\n                 (form.get('iso2').dirty || form.get('iso2').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso2').errors?.required\">\n                  ISO 3166-1 ALPHA-2 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.pattern\">\n                  ISO 3166-1 ALPHA-2 country code requires 2 letters.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-2 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n              <div class=\"form-group\">\n            <label for=\"iso3\">ISO 3166-1 ALPHA-3 Country Code (3 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso3\"\n                   formControlName=\"iso3\" required\n                   placeholder=\"3 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso3').invalid &&\n                 (form.get('iso3').dirty || form.get('iso3').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso3').errors?.required\">\n                  ISO 3166-1 ALPHA-3 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.pattern\">\n                  ISO 3166-1 ALPHA-3 country code requires 3 letters.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-3 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/countries']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/countries/country-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormBuilder, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { map } from 'rxjs/operators';\nimport { Observable } from 'rxjs';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { Country } from '../countries/country';\nimport { CountryService } from './country.service';\n\n@Component({\n  selector: 'app-country-edit',\n  templateUrl: './country-edit.component.html',\n  styleUrls: ['./country-edit.component.css']\n})\nexport class CountryEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  country: Country;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new country,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  constructor(\n    private fb: FormBuilder,\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private countryService: CountryService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = this.fb.group({\n      name: ['',\n        Validators.required,\n        this.isDupeField(\"name\")\n      ],\n      iso2: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{2}/)\n        ],\n        this.isDupeField(\"iso2\")\n      ],\n      iso3: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{3}/)\n        ],\n        this.isDupeField(\"iso3\")\n      ]\n    });\n\n    this.loadData();\n  }\n\n  loadData() {\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the country from the server\n      this.countryService.get<Country>(this.id)\n        .subscribe(result => {\n          this.country = result;\n          this.title = \"Edit - \" + this.country.name;\n\n          // update the form with the country value\n          this.form.patchValue(this.country);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new Country\";\n    }\n  }\n\n  onSubmit() {\n\n    var country = (this.id) ? this.country : <Country>{};\n\n    country.name = this.form.get(\"name\").value;\n    country.iso2 = this.form.get(\"iso2\").value;\n    country.iso3 = this.form.get(\"iso3\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.countryService\n        .put<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + country.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.countryService\n        .post<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeField(fieldName: string): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n\n      var countryId = (this.id) ? this.id.toString() : \"0\";\n\n      return this.countryService.isDupeField(\n        countryId,\n        fieldName,\n        control.value)\n        .pipe(map(result => {\n          return (result ? { isDupeField: true } : null);\n        }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/countries/country.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CountryService\n    extends BaseService {\n    constructor(\n        http: HttpClient,\n        @Inject('BASE_URL') baseUrl: string) {\n        super(http, baseUrl);\n    }\n\n    getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Countries';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    get<Country>(id): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries/\" + id;\n        return this.http.get<Country>(url);\n    }\n\n    put<Country>(item): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries/\" + item.id;\n        return this.http.put<Country>(url, item);\n    }\n\n    post<Country>(item): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries\";\n        return this.http.post<Country>(url, item);\n    }\n\n    isDupeField(countryId, fieldName, fieldValue): Observable<boolean> {\n        var params = new HttpParams()\n            .set(\"countryId\", countryId)\n            .set(\"fieldName\", fieldName)\n            .set(\"fieldValue\", fieldValue);\n        var url = this.baseUrl + \"api/Countries/IsDupeField\";\n        return this.http.post<boolean>(url, null, { params });\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/countries/country.ts",
    "content": "export interface Country {\n    id: number;\n    name: string;\n    iso2: string;\n    iso3: string;\n    totCities: number;\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">WorldCities</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/cities']\"\n              >Cities</a\n            >\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/countries']\"\n              >Countries</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>WorldCities</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n      <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n</head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/main.ts",
    "content": "import 'hammerjs';\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_08/WorldCities/Controllers/CitiesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CitiesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CitiesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Cities/?pageIndex=0&pageSize=10\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CityDTO>>> GetCities(\n                int pageIndex = 0,\n                int pageSize = 10,\n                string sortColumn = null,\n                string sortOrder = null,\n                string filterColumn = null,\n                string filterQuery = null)\n        {\n            return await ApiResult<CityDTO>.CreateAsync(\n                    _context.Cities\n                        .Select(c => new CityDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            Lat = c.Lat,\n                            Lon = c.Lon,\n                            CountryId = c.Country.Id,\n                            CountryName = c.Country.Name\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        // GET: api/Cities/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<City>> GetCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            return city;\n        }\n\n        // PUT: api/Cities/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCity(int id, City city)\n        {\n            if (id != city.Id)\n            {\n                return BadRequest();\n            }\n\n            //var sourceCity = _context.Cities.Where(i => i.Id == city.Id).FirstOrDefault();\n            //if (sourceCity == null) return BadRequest();\n            //sourceCity.Name = city.Name;\n            //sourceCity.Lat = city.Lat;\n            //sourceCity.Lon = city.Lon;\n\n            _context.Entry(city).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CityExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Cities\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<City>> PostCity(City city)\n        {\n            _context.Cities.Add(city);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCity\", new { id = city.Id }, city);\n        }\n\n        // DELETE: api/Cities/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<City>> DeleteCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            _context.Cities.Remove(city);\n            await _context.SaveChangesAsync();\n\n            return city;\n        }\n\n        private bool CityExists(int id)\n        {\n            return _context.Cities.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeCity\")]\n        public bool IsDupeCity(City city)\n        {\n            return _context.Cities.Any(\n                e => e.Name == city.Name\n                && e.Lat == city.Lat\n                && e.Lon == city.Lon\n                && e.CountryId == city.CountryId\n                && e.Id != city.Id);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Controllers/CountriesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Dynamic.Core;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CountriesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CountriesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Countries/?pageIndex=0&pageSize=10\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CountryDTO>>> GetCountries(\n        int pageIndex = 0,\n        int pageSize = 10,\n        string sortColumn = null,\n        string sortOrder = null,\n        string filterColumn = null,\n        string filterQuery = null)\n        {\n            return await ApiResult<CountryDTO>.CreateAsync(\n                    _context.Countries\n                        .Select(c => new CountryDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            ISO2 = c.ISO2,\n                            ISO3 = c.ISO3,\n                            TotCities = c.Cities.Count\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        //public async Task<ActionResult<ApiResult<dynamic>>> GetCountries(\n        //    int pageIndex = 0,\n        //    int pageSize = 10,\n        //    string sortColumn = null,\n        //    string sortOrder = null,\n        //    string filterColumn = null,\n        //    string filterQuery = null)\n        //{\n        //    return await ApiResult<dynamic>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n\n        //public async Task<ActionResult<ApiResult<Country>>> GetCountries(\n        //int pageIndex = 0,\n        //int pageSize = 10,\n        //string sortColumn = null,\n        //string sortOrder = null,\n        //string filterColumn = null,\n        //string filterQuery = null)\n        //{\n        //    return await ApiResult<Country>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new Country()\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n        // GET: api/Countries/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<Country>> GetCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            return country;\n        }\n\n        // PUT: api/Countries/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCountry(int id, Country country)\n        {\n            if (id != country.Id)\n            {\n                return BadRequest();\n            }\n\n            _context.Entry(country).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CountryExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Countries\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<Country>> PostCountry(Country country)\n        {\n            _context.Countries.Add(country);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCountry\", new { id = country.Id }, country);\n        }\n\n        // DELETE: api/Countries/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<Country>> DeleteCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            _context.Countries.Remove(country);\n            await _context.SaveChangesAsync();\n\n            return country;\n        }\n\n        private bool CountryExists(int id)\n        {\n            return _context.Countries.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeField\")]\n        public bool IsDupeField(\n            int countryId, \n            string fieldName, \n            string fieldValue)\n        {\n            // Standard approach(using strongly-typed LAMBA expressions)\n            //switch (fieldName)\n            //{\n            //    case \"name\":\n            //        return _context.Countries.Any(\n            //            c => c.Name == fieldValue && c.Id != countryId);\n            //    case \"iso2\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO2 == fieldValue && c.Id != countryId);\n            //    case \"iso3\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO3 == fieldValue && c.Id != countryId);\n            //    default:\n            //        return false;\n            //}\n\n            // Dynamic approach (using System.Linq.Dynamic.Core)\n            return (ApiResult<Country>.IsValidProperty(fieldName, true))\n                ? _context.Countries.Any(\n                    String.Format(\"{0} == @0 && Id != @1\", fieldName),\n                    fieldValue,\n                    countryId)\n                : false;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Controllers/SeedController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing WorldCities.Data;\nusing OfficeOpenXml;\nusing System.IO;\nusing Microsoft.AspNetCore.Hosting;\nusing WorldCities.Data.Models;\nusing System.Text.Json;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]/[action]\")]\n    [ApiController]\n    public class SeedController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n        private readonly IWebHostEnvironment _env;\n\n        public SeedController(\n            ApplicationDbContext context, \n            IWebHostEnvironment env)\n        {\n            _context = context;\n            _env = env;\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> Import()\n        {\n            // NOTE: This method has been updated on 2020.09.13.\n            // The new version is more efficient than the code described in the book's Chapter 4.\n            // ref.: https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/issues/15\n\n            var path = Path.Combine(\n                _env.ContentRootPath,\n                String.Format(\"Data/Source/worldcities.xlsx\"));\n\n            using (var stream = new FileStream(\n                path,\n                FileMode.Open,\n                FileAccess.Read))\n            {\n                using (var ep = new ExcelPackage(stream))\n                {\n                    // get the first worksheet\n\n                    var ws = ep.Workbook.Worksheets[0];\n\n                    // initialize the record counters\n                    var nCountries = 0;\n                    var nCities = 0;\n\n                    #region Import all Countries\n                    // create a list containing all the countries already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCountries = _context.Countries.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n                        var name = row[nRow, 5].GetValue<string>();\n\n                        // does this country already exist in the database?\n                        if (lstCountries.Where(c => c.Name == name).Count() == 0)\n                        {\n                            // create the Country entity and fill it with xlsx data\n                            var country = new Country();\n                            country.Name = name;\n                            country.ISO2 = row[nRow, 6].GetValue<string>();\n                            country.ISO3 = row[nRow, 7].GetValue<string>();\n\n                            // add the new country to the DB context\n                            _context.Countries.Add(country);\n\n                            // store the country to retrieve its Id later on\n                            lstCountries.Add(country);\n\n                            // increment the counter\n                            nCountries++;\n                        }\n                    }\n\n                    // save all the countries into the Database\n                    if (nCountries > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    #region Import all Cities\n                    // create a list containing all the cities already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCities = _context.Cities.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n\n                        var name = row[nRow, 1].GetValue<string>();\n                        var name_ASCII = row[nRow, 2].GetValue<string>();\n                        var countryName = row[nRow, 5].GetValue<string>();\n                        var lat = row[nRow, 3].GetValue<decimal>();\n                        var lon = row[nRow, 4].GetValue<decimal>();\n                        // retrieve country and countryId\n                        var country = lstCountries.Where(c => c.Name == countryName)\n                            .FirstOrDefault();\n                        var countryId = country.Id;\n\n                        // does this city already exist in the database?\n                        if (lstCities.Where(\n                            c => c.Name == name\n                            && c.Lat == lat\n                            && c.Lon == lon\n                            && c.CountryId == countryId\n                        ).Count() == 0)\n                        {\n                            // create the City entity and fill it with xlsx data\n                            var city = new City();\n                            city.Name = name;\n                            city.Name_ASCII = name_ASCII;\n                            city.Lat = lat;\n                            city.Lon = lon;\n                            city.CountryId = countryId;\n\n                            // add the new city to the DB context\n                            _context.Cities.Add(city);\n\n                            // increment the counter\n                            nCities++;\n                        }\n                    }\n\n                    // save all the cities into the Database\n                    if (nCities > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    return new JsonResult(new\n                    {\n                        Cities = nCities,\n                        Countries = nCountries\n                    });\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Chapter_08/WorldCities/Data/ApiResult.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Linq.Dynamic.Core;\nusing System.Reflection;\nusing System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class ApiResult<T>\n    {\n        /// <summary>\n        /// Private constructor called by the CreateAsync method.\n        /// </summary>\n        private ApiResult(\n            List<T> data,\n            int count,\n            int pageIndex,\n            int pageSize,\n            string sortColumn,\n            string sortOrder,\n            string filterColumn,\n            string filterQuery)\n        {\n            Data = data;\n            PageIndex = pageIndex;\n            PageSize = pageSize;\n            TotalCount = count;\n            TotalPages = (int)Math.Ceiling(count / (double)pageSize);\n            SortColumn = sortColumn;\n            SortOrder = sortOrder;\n            FilterColumn = filterColumn;\n            FilterQuery = filterQuery;\n        }\n\n        #region Methods\n        /// <summary>\n        /// Pages, sorts and/or filters a IQueryable source.\n        /// </summary>\n        /// <param name=\"source\">An IQueryable source of generic type</param>\n        /// <param name=\"pageIndex\">Zero-based current page index (0 = first page)</param>\n        /// <param name=\"pageSize\">The actual size of each page</param>\n        /// <param name=\"sortColumn\">The sorting colum name</param>\n        /// <param name=\"sortOrder\">The sorting order (\"ASC\" or \"DESC\")</param>\n        /// <param name=\"filterColumn\">The filtering column name</param>\n        /// <param name=\"filterQuery\">The filtering query (value to lookup)</param>\n        /// <returns>\n        /// A object containing the IQueryable paged/sorted/filtered result \n        /// and all the relevant paging/sorting/filtering navigation info.\n        /// </returns>\n        public static async Task<ApiResult<T>> CreateAsync(\n            IQueryable<T> source,\n            int pageIndex,\n            int pageSize,\n            string sortColumn = null,\n            string sortOrder = null,\n            string filterColumn = null,\n            string filterQuery = null)\n        {\n            if (!String.IsNullOrEmpty(filterColumn)\n                && !String.IsNullOrEmpty(filterQuery)\n                && IsValidProperty(filterColumn))\n            {\n                source = source.Where(\n                    String.Format(\"{0}.Contains(@0)\",\n                    filterColumn),\n                    filterQuery);\n            }\n\n            var count = await source.CountAsync();\n\n            if (!String.IsNullOrEmpty(sortColumn)\n                && IsValidProperty(sortColumn))\n            {\n                sortOrder = !String.IsNullOrEmpty(sortOrder)\n                    && sortOrder.ToUpper() == \"ASC\"\n                    ? \"ASC\"\n                    : \"DESC\";\n                source = source.OrderBy(\n                    String.Format(\n                        \"{0} {1}\",\n                        sortColumn,\n                        sortOrder)\n                    );\n            }\n\n            source = source\n                .Skip(pageIndex * pageSize)\n                .Take(pageSize);\n\n            // retrieve the SQL query (for debug purposes)\n            #if DEBUG\n            {\n                var sql = source.ToSql();\n                // do something with the sql string\n            }\n            #endif\n\n            var data = await source.ToListAsync();\n\n            return new ApiResult<T>(\n                data,\n                count,\n                pageIndex,\n                pageSize,\n                sortColumn,\n                sortOrder,\n                filterColumn,\n                filterQuery);\n        }\n\n        /// <summary>\n        /// Checks if the given property name exists\n        /// to protect against SQL injection attacks\n        /// </summary>\n        public static bool IsValidProperty(\n            string propertyName,\n            bool throwExceptionIfNotFound = true)\n        {\n            var prop = typeof(T).GetProperty(\n                propertyName,\n                BindingFlags.IgnoreCase |\n                BindingFlags.Public |\n                BindingFlags.Static |\n                BindingFlags.Instance);\n            if (prop == null && throwExceptionIfNotFound)\n                throw new NotSupportedException(\n                    String.Format(\n                        \"ERROR: Property '{0}' does not exist.\",\n                        propertyName)\n                    );\n            return prop != null;\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The data result.\n        /// </summary>\n        public List<T> Data { get; private set; }\n\n        /// <summary>\n        /// Zero-based index of current page.\n        /// </summary>\n        public int PageIndex { get; private set; }\n\n        /// <summary>\n        /// Number of items contained in each page.\n        /// </summary>\n        public int PageSize { get; private set; }\n\n        /// <summary>\n        /// Total items count\n        /// </summary>\n        public int TotalCount { get; private set; }\n\n        /// <summary>\n        /// Total pages count\n        /// </summary>\n        public int TotalPages { get; private set; }\n\n        /// <summary>\n        /// TRUE if the current page has a previous page, FALSE otherwise.\n        /// </summary>\n        public bool HasPreviousPage\n        {\n            get\n            {\n                return (PageIndex > 0);\n            }\n        }\n\n        /// <summary>\n        /// TRUE if the current page has a next page, FALSE otherwise.\n        /// </summary>\n        public bool HasNextPage\n        {\n            get\n            {\n                return ((PageIndex +1) < TotalPages);\n            }\n        }\n\n        /// <summary>\n        /// Sorting Column name (or null if none set)\n        /// </summary>\n        public string SortColumn { get; set; }\n\n        /// <summary>\n        /// Sorting Order (\"ASC\", \"DESC\" or null if none set)\n        /// </summary>\n        public string SortOrder { get; set; }\n\n        /// <summary>\n        /// Filter Column name (or null if none set)\n        /// </summary>\n        public string FilterColumn { get; set; }\n\n        /// <summary>\n        /// Filter Query string \n        /// (to be used within the given FilterColumn)\n        /// </summary>\n        public string FilterQuery { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Data/ApplicationDbContext.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Data\n{\n    public class ApplicationDbContext : DbContext\n    {\n        #region Constructor\n        public ApplicationDbContext() : base()\n        {\n        }\n\n        public ApplicationDbContext(DbContextOptions options) : base(options)\n        {\n        }\n        #endregion Constructor\n\n        #region Methods\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            base.OnModelCreating(modelBuilder);\n\n            // Map Entity names to DB Table names\n            modelBuilder.Entity<City>().ToTable(\"Cities\");\n            modelBuilder.Entity<Country>().ToTable(\"Countries\");\n        }\n        #endregion Methods\n\n        #region Properties\n        public DbSet<City> Cities { get; set; }\n        public DbSet<Country> Countries { get; set; }\n        #endregion Properties\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Data/CityDTO.cs",
    "content": "﻿namespace WorldCities.Data\n{\n    public class CityDTO\n    {\n        public CityDTO() { }\n\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        public string Name_ASCII { get; set; }\n\n        public decimal Lat { get; set; }\n\n        public decimal Lon { get; set; }\n\n        public int CountryId { get; set; }\n\n        public string CountryName { get; set; }\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Data/CountryDTO.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class CountryDTO\n    {\n        public CountryDTO() { }\n\n        #region Properties\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n\n        public int TotCities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Data/IQueryableExtensions.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Query;\nusing Microsoft.EntityFrameworkCore.Query.SqlExpressions;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data\n{\n    public static class IQueryableExtension\n    {\n        public static string ToSql<T>(this IQueryable<T> query)\n        {\n            var enumerator = query.Provider\n                .Execute<IEnumerable<T>>(query.Expression).GetEnumerator();\n            var relationalCommandCache = enumerator\n                .Private(\"_relationalCommandCache\");\n            var selectExpression = relationalCommandCache\n                .Private<SelectExpression>(\"_selectExpression\");\n            var factory = relationalCommandCache\n                .Private<IQuerySqlGeneratorFactory>(\"_querySqlGeneratorFactory\");\n\n            var sqlGenerator = factory.Create();\n            var command = sqlGenerator.GetCommand(selectExpression);\n\n            string sql = command.CommandText;\n            return sql;\n        }\n\n        private static object Private(this object obj, string privateField) =>\n            obj?.GetType()\n            .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?\n            .GetValue(obj);\n        private static T Private<T>(this object obj, string privateField) =>\n            (T)obj?\n            .GetType()\n            .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?\n            .GetValue(obj);\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Data/Migrations/20191123030140_Initial.Designer.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    [Migration(\"20191123030140_Initial\")]\n    partial class Initial\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Data/Migrations/20191123030140_Initial.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace WorldCities.Data.Migrations\n{\n    public partial class Initial : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.CreateTable(\n                name: \"Countries\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    ISO2 = table.Column<string>(nullable: true),\n                    ISO3 = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Countries\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"Cities\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    Name_ASCII = table.Column<string>(nullable: true),\n                    Lat = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    Lon = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    CountryId = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Cities\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_Cities_Countries_CountryId\",\n                        column: x => x.CountryId,\n                        principalTable: \"Countries\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_Cities_CountryId\",\n                table: \"Cities\",\n                column: \"CountryId\");\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.DropTable(\n                name: \"Cities\");\n\n            migrationBuilder.DropTable(\n                name: \"Countries\");\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Data/Migrations/ApplicationDbContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    partial class ApplicationDbContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Data/Models/City.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class City\n    {\n        #region Constructor\n        public City()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this City\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// City name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// City name (in ASCII format)\n        /// </summary>\n        public string Name_ASCII { get; set; }\n\n        /// <summary>\n        /// City latitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lat { get; set; }\n\n        /// <summary>\n        /// City longitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lon { get; set; }\n\n        /// <summary>\n        /// Country Id (foreign key)\n        /// </summary>\n        [ForeignKey(\"Country\")]\n        public int CountryId { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// The country related to this city.\n        /// </summary>\n        public virtual Country Country { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Data/Models/Country.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class Country\n    {\n        #region Constructor\n        public Country()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this Country\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// Country name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-2 format)\n        /// </summary>\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-3 format)\n        /// </summary>\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n        #endregion\n\n        #region Client-side properties\n        /// <summary>\n        /// The number of cities related to this country.\n        /// </summary>\n        [NotMapped]\n        public int TotCities\n        {\n            get\n            {\n                return (Cities != null)\n                    ? Cities.Count\n                    : _TotCities;\n            }\n            set { _TotCities = value; }\n        }\n\n        private int _TotCities = 0;\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// A list containing all the cities related to this country.\n        /// </summary>\n        [JsonIgnore]\n        public virtual List<City> Cities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_08/WorldCities/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Pages/_ViewImports.cshtml",
    "content": "@using WorldCities\n@namespace WorldCities.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_08/WorldCities/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing System.Text.Json;\nusing WorldCities.Data;\n\nnamespace WorldCities\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews()\n                .AddJsonOptions(options => {\n                    // set this option to TRUE to indent the JSON output\n                    options.JsonSerializerOptions.WriteIndented = true;\n                    // set this option to NULL to use PascalCase instead of CamelCase (default)\n                    // options.JsonSerializerOptions.PropertyNamingPolicy = null;\n                });\n\n\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            // Add EntityFramework support for SqlServer.\n            services.AddEntityFrameworkSqlServer();\n\n            // Add ApplicationDbContext.\n            services.AddDbContext<ApplicationDbContext>(options =>\n                options.UseSqlServer(\n                    Configuration.GetConnectionString(\"DefaultConnection\")\n                    )\n            );\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles();\n            }\n\n            app.UseRouting();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/WorldCities.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"EPPlus\" Version=\"4.5.3.2\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Debug\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" Version=\"3.1.0\" />\n    <PackageReference Include=\"System.Linq.Dynamic.Core\" Version=\"1.0.20\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_08/WorldCities/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_08/WorldCities/appsettings.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"Server=localhost\\\\SQLEXPRESS;Database=WorldCities;User Id=WorldCities;Password=MyVeryOwn$721;Integrated Security=False;MultipleActiveResultSets=True\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "Chapter_08.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_08\\WorldCities\\WorldCities.csproj\", \"{4ED25081-BB64-42BE-9714-1A84F532A324}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{4ED25081-BB64-42BE-9714-1A84F532A324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{4ED25081-BB64-42BE-9714-1A84F532A324}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{4ED25081-BB64-42BE-9714-1A84F532A324}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{4ED25081-BB64-42BE-9714-1A84F532A324}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_09/WorldCities/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/README.md",
    "content": "# WorldCities\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"WorldCities\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"WorldCities:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": [],\n            \"assets\": [\n              \"src/assets\"\n            ]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\n              \"src/tsconfig.app.json\",\n              \"src/tsconfig.spec.json\"\n            ],\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"WorldCities-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"WorldCities:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"WorldCities\"\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/package.json",
    "content": "{\n  \"name\": \"worldcities\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run WorldCities:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/cdk\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/material\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"hammerjs\": \"2.0.8\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/angular-material.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatPaginatorModule } from '@angular/material/paginator';\nimport { MatSortModule } from '@angular/material/sort';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\n\n@NgModule({\n  imports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ],\n  exports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ]\n})\n\nexport class AngularMaterialModule { }\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/app.component.html",
    "content": "<body>\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { BaseFormComponent } from './base.form.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { CitiesComponent } from './cities/cities.component';\nimport { CityEditComponent } from './cities/city-edit.component';\nimport { CountriesComponent } from './countries/countries.component';\nimport { CountryEditComponent } from './countries/country-edit.component';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { AngularMaterialModule } from './angular-material.module';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    BaseFormComponent,\n    NavMenuComponent,\n    HomeComponent,\n    CitiesComponent,\n    CityEditComponent,\n    CountriesComponent,\n    CountryEditComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    RouterModule.forRoot([\n      { path: '', component: HomeComponent, pathMatch: 'full' },\n      { path: 'cities', component: CitiesComponent },\n      { path: 'city/:id', component: CityEditComponent },\n      { path: 'city', component: CityEditComponent },\n      { path: 'countries', component: CountriesComponent },\n      { path: 'country/:id', component: CountryEditComponent },\n      { path: 'country', component: CountryEditComponent }\n    ]),\n    BrowserAnimationsModule,\n    AngularMaterialModule,\n    ReactiveFormsModule\n    ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/base.form.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { FormGroup } from '@angular/forms';\n\n@Component({\n    template: ''\n})  \nexport class BaseFormComponent {\n\n    // the form model\n    form: FormGroup;\n\n    constructor() {\n    }\n\n    // retrieve a FormControl\n    getControl(name: string) {\n        return this.form.get(name);\n    }\n\n    // returns TRUE if the FormControl is valid\n    isValid(name: string) {\n        var e = this.getControl(name);\n        return e && e.valid;\n    }\n\n    // returns TRUE if the FormControl has been changed\n    isChanged(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched);\n    }\n\n    // returns TRUE if the FormControl is raising an error,\n    // i.e. an invalid state after user changes\n    hasError(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched) && e.invalid;\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/base.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Observable } from 'rxjs';\n\n@Injectable()\nexport abstract class BaseService {\n    constructor(\n        public http: HttpClient,\n        public baseUrl: string\n    ) {\n    }\n\n    abstract getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string): Observable<ApiResult>;\n\n    abstract get<T>(id: number): Observable<T>;\n    abstract put<T>(item: T): Observable<T>;\n    abstract post<T>(item: T): Observable<T>;\n}\n\nexport interface ApiResult<T> {\n    data: T[];\n    pageIndex: number;\n    pageSize: number;\n    totalCount: number;\n    totalPages: number;\n    sortColumn: string;\n    sortOrder: string;\n    filterColumn: string;\n    filterQuery: string;\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/cities/cities.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/cities/cities.component.html",
    "content": "<h1>Cities</h1>\n\n<p>Here's a list of cities: feel free to play with it.</p>\n\n<p *ngIf=\"!cities\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"cities\">\n  <button type=\"button\"\n          [routerLink]=\"['/city']\"\n          class=\"btn btn-success\">\n      Add a new City\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!cities\">\n    <input matInput (keyup)=\"loadData($event.target.value)\"\n        placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"cities\" class=\"mat-elevation-z8\" [hidden]=\"!cities\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/city', city.id]\">{{city.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"lat\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Latitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lat}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"lon\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Longitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lon}} </td>\n  </ng-container>\n\n  <!-- CountryName Column -->\n  <ng-container matColumnDef=\"countryName\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Country</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/country', city.countryId]\">{{city.countryName}}</a>\n    </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!cities\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/cities/cities.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { RouterTestingModule } from '@angular/router/testing';\nimport { AngularMaterialModule } from '../angular-material.module';\nimport { of } from 'rxjs';\n\nimport { CitiesComponent } from './cities.component';\nimport { City } from './city';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\ndescribe('CitiesComponent', () => {\n  let fixture: ComponentFixture<CitiesComponent>;\n  let component: CitiesComponent;\n\n  // async beforeEach(): TestBed initialization\n  beforeEach(async(() => {\n\n    // Create a mock cityService object with a mock 'getData' method\n    let cityService = jasmine.createSpyObj<CityService>(\n      'CityService', ['getData']\n    );\n\n    // Configure the 'getData' spy method\n    cityService.getData.and.returnValue(\n      // return an Observable with some test data\n      of<ApiResult<City>>(<ApiResult<City>>{\n        data: [\n          <City>{\n            name: 'TestCity1',\n            id: 1, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          },\n          <City>{\n            name: 'TestCity2',\n            id: 2, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          },\n          <City>{\n            name: 'TestCity3',\n            id: 3, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          }\n        ],\n        totalCount: 3,\n        pageIndex: 0,\n        pageSize: 10\n      }));\n\n    TestBed.configureTestingModule({\n      declarations: [CitiesComponent],\n      imports: [\n        BrowserAnimationsModule,\n        AngularMaterialModule,\n        RouterTestingModule\n      ],\n      providers: [\n        {\n          provide: CityService,\n          useValue: cityService\n        }\n      ]\n    })\n      .compileComponents();\n  }));\n\n  // synchronous beforeEach(): fixtures and components setup\n  beforeEach(() => {\n    fixture = TestBed.createComponent(CitiesComponent);\n    component = fixture.componentInstance;\n\n    component.paginator = jasmine.createSpyObj(\n      \"MatPaginator\", [\"length\", \"pageIndex\", \"pageSize\"]\n    );\n\n    fixture.detectChanges();\n  });\n\n  it('should display a \"Cities\" title', async(() => {\n    let title = fixture.nativeElement\n      .querySelector('h1');\n    expect(title.textContent).toEqual('Cities');\n  }));\n\n  it('should contain a table with a list of one or more cities', async(() => {\n    let table = fixture.nativeElement\n      .querySelector('table.mat-table');\n    let tableRows = table\n      .querySelectorAll('tr.mat-row');\n    expect(tableRows.length).toBeGreaterThan(0);\n  }));\n});\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/cities/cities.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { City } from './city';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-cities',\n  templateUrl: './cities.component.html',\n  styleUrls: ['./cities.component.css']\n})\nexport class CitiesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'lat', 'lon', 'countryName'];\n  public cities: MatTableDataSource<City>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private cityService: CityService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n    var sortColumn = (this.sort)\n      ? this.sort.active\n      : this.defaultSortColumn;\n\n    var sortOrder = (this.sort)\n      ? this.sort.direction\n      : this.defaultSortOrder;\n\n    var filterColumn = (this.filterQuery)\n      ? this.defaultFilterColumn\n      : null;\n\n    var filterQuery = (this.filterQuery)\n      ? this.filterQuery\n      : null;\n\n    this.cityService.getData<ApiResult<City>>(\n      event.pageIndex,\n      event.pageSize,\n      sortColumn,\n      sortOrder,\n      filterColumn,\n      filterQuery)\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.cities = new MatTableDataSource<City>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/cities/city-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/cities/city-edit.component.html",
    "content": "<div class=\"city-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !city\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n\n        <div *ngIf=\"form.invalid && form.errors?.isDupeCity\"\n             class=\"alert alert-danger\">\n              <strong>ERROR</strong>:\n              A city with the same <i>name</i>, <i>lat</i>,\n              <i>lon</i> and <i>country</i> already exists.\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"name\">City name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"City name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"hasError('name')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"lat\">City latitude:</label>\n            <br />\n            <input type=\"text\" id=\"lat\"\n                   formControlName=\"lat\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lat')\"\n                  class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lat').errors?.required\">\n                  Latitude is required.\n                </div>\n                <div *ngIf=\"form.get('lat').errors?.pattern\">\n                  Latitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n\n\n        <div class=\"form-group\">\n            <label for=\"lon\">City longitude:</label>\n            <br />\n            <input type=\"text\" id=\"lon\"\n                   formControlName=\"lon\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lon')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lon').errors?.required\">\n                  Longitude is required.\n                </div>\n                <div *ngIf=\"form.get('lon').errors?.pattern\">\n                  Longitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"countryId\">Country:</label>\n            <br />\n            <mat-form-field *ngIf=\"countries\">\n              <mat-label>Select a Country...</mat-label>\n              <mat-select id=\"countryId\" formControlName=\"countryId\">\n                <mat-option *ngFor=\"let country of countries\" [value]=\"country.id\">\n                  {{country.name}}\n                </mat-option>\n              </mat-select>\n            </mat-form-field>\n\n            <div *ngIf=\"hasError('countryId')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('countryId').errors?.required\">\n                  Please select a Country.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/cities']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n\n<!-- Form debug info panel -->\n<div class=\"card bg-light mb-3\">\n  <div class=\"card-header\">Form Debug Info</div>\n  <div class=\"card-body\">\n    <div class=\"card-text\">\n      <div><strong>Form value:</strong></div>\n      <div class=\"help-block\">\n          {{ form.value | json }}\n      </div>\n      <div class=\"mt-2\"><strong>Form status:</strong></div>\n      <div class=\"help-block\">\n          {{ form.status | json }}\n      </div>\n    </div>\n  </div>\n</div>\n\n<!-- Form activity log panel -->\n<div class=\"card bg-light mb-3\">\n  <div class=\"card-header\">Form Activity Log</div>\n  <div class=\"card-body\">\n    <div class=\"card-text\">\n      <div class=\"help-block\">\n        <span *ngIf=\"activityLog\" \n            [innerHTML]=\"activityLog\"></span>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/cities/city-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormControl, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { City } from './city';\nimport { Country } from '../countries/country';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-city-edit',\n  templateUrl: './city-edit.component.html',\n  styleUrls: ['./city-edit.component.css']\n})\nexport class CityEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  city: City;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new city,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  // the countries array for the select\n  countries: Country[];\n\n  // Activity Log (for debugging purposes)\n  activityLog: string = '';\n\n  constructor(\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private cityService: CityService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = new FormGroup({\n      name: new FormControl('', Validators.required),\n      lat: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      lon: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      countryId: new FormControl('', Validators.required)\n    }, null, this.isDupeCity());\n\n    // react to form changes\n    this.form.valueChanges\n      .subscribe(val => {\n        if (!this.form.dirty) {\n          this.log(\"Form Model has been loaded.\");\n        }\n        else {\n          this.log(\"Form was updated by the user.\");\n        }\n      });\n\n    // react to changes in the form.name control\n    this.form.get(\"name\")!.valueChanges\n      .subscribe(val => {\n        if (!this.form.dirty) {\n          this.log(\"Name has been loaded with initial values.\");\n        }\n        else {\n          this.log(\"Name was updated by the user.\");\n        }\n      });\n\n    this.loadData();\n  }\n\n  log(str: string) {\n    this.activityLog += \"[\"\n      + new Date().toLocaleString()\n      + \"] \" + str + \"<br />\";\n  }\n\n  loadData() {\n\n    // load countries\n    this.loadCountries();\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the city from the server\n      this.cityService.get<City>(this.id).subscribe(result => {\n        this.city = result;\n        this.title = \"Edit - \" + this.city.name;\n\n        // update the form with the city value\n        this.form.patchValue(this.city);\n      }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new City\";\n    }\n  }\n\n  loadCountries() {\n    // fetch all the countries from the server\n    this.cityService.getCountries<ApiResult<Country>>(\n      0,\n      9999,\n      \"name\",\n      null,\n      null,\n      null,\n    ).subscribe(result => {\n      this.countries = result.data;\n    }, error => console.error(error));\n  }\n\n  onSubmit() {\n\n    var city = (this.id) ? this.city : <City>{};\n\n    city.name = this.form.get(\"name\").value;\n    city.lat = +this.form.get(\"lat\").value;\n    city.lon = +this.form.get(\"lon\").value;\n    city.countryId = +this.form.get(\"countryId\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.cityService\n        .put<City>(city)\n        .subscribe(result => {\n\n          console.log(\"City \" + city.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.cityService\n        .post<City>(city)\n        .subscribe(result => {\n\n          console.log(\"City \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeCity(): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n      var city = <City>{};\n      city.id = (this.id) ? this.id : 0;\n      city.name = this.form.get(\"name\").value;\n      city.lat = +this.form.get(\"lat\").value;\n      city.lon = +this.form.get(\"lon\").value;\n      city.countryId = +this.form.get(\"countryId\").value;\n\n      return this.cityService.isDupeCity(city)\n        .pipe(map(result => {\n          return (result ? { isDupeCity: true } : null);\n        }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/cities/city.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CityService\n    extends BaseService {\n    constructor(\n        http: HttpClient,\n        @Inject('BASE_URL') baseUrl: string) {\n        super(http, baseUrl);\n    }\n\n    getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Cities';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    get<City>(id): Observable<City> {\n        var url = this.baseUrl + \"api/Cities/\" + id;\n        return this.http.get<City>(url);\n    }\n\n    put<City>(item): Observable<City> {\n        var url = this.baseUrl + \"api/Cities/\" + item.id;\n        return this.http.put<City>(url, item);\n    }\n\n    post<City>(item): Observable<City> {\n        var url = this.baseUrl + \"api/Cities\";\n        return this.http.post<City>(url, item);\n    }\n\n    getCountries<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Countries';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    isDupeCity(item): Observable<boolean> {\n        var url = this.baseUrl + \"api/Cities/IsDupeCity\";\n        return this.http.post<boolean>(url, item);\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/cities/city.ts",
    "content": "export interface City {\n    id: number;\n    name: string;\n    lat: number;\n    lon: number;\n    countryId: number;\n    countryName: string;\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/countries/countries.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/countries/countries.component.html",
    "content": "<h1>Countries</h1>\n\n<p>Here's a list of countries: feel free to play with it.</p>\n\n<p *ngIf=\"!countries\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"countries\">\n  <button type=\"button\"\n          [routerLink]=\"['/country']\"\n          class=\"btn btn-success\">\n      Add a new Country\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!countries\">\n  <input matInput (keyup)=\"loadData($event.target.value)\"\n      placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"countries\" class=\"mat-elevation-z8\" [hidden]=\"!countries\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let country\">\n      <a [routerLink]=\"['/country', country.id]\">{{country.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"iso2\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 2</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso2}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"iso3\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 3</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso3}} </td>\n  </ng-container>\n\n  <!-- TotCities Column -->\n  <ng-container matColumnDef=\"totCities\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Tot. Cities</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.totCities}} </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!countries\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/countries/countries.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { Country } from './country';\nimport { CountryService } from './country.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-countries',\n  templateUrl: './countries.component.html',\n  styleUrls: ['./countries.component.css']\n})\nexport class CountriesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'iso2', 'iso3', 'totCities'];\n  public countries: MatTableDataSource<Country>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private countryService: CountryService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n    var sortColumn = (this.sort)\n      ? this.sort.active\n      : this.defaultSortColumn;\n\n    var sortOrder = (this.sort)\n      ? this.sort.direction\n      : this.defaultSortOrder;\n\n    var filterColumn = (this.filterQuery)\n      ? this.defaultFilterColumn\n      : null;\n\n    var filterQuery = (this.filterQuery)\n      ? this.filterQuery\n      : null;\n\n    this.countryService.getData<ApiResult<Country>>(\n      event.pageIndex,\n      event.pageSize,\n      sortColumn,\n      sortOrder,\n      filterColumn,\n      filterQuery)\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.countries = new MatTableDataSource<Country>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/countries/country-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/countries/country-edit.component.html",
    "content": "<div class=\"country-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !country\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n     \n        <div class=\"form-group\">\n            <label for=\"name\">Country name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"Country name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"form.get('name').invalid &&\n                 (form.get('name').dirty || form.get('name').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n                <div *ngIf=\"form.get('name').errors?.isDupeField\">\n                  Name does already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"iso2\">ISO 3166-1 ALPHA-2 Country Code (2 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso2\"\n                   formControlName=\"iso2\" required\n                   placeholder=\"2 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso2').invalid &&\n                 (form.get('iso2').dirty || form.get('iso2').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso2').errors?.required\">\n                  ISO 3166-1 ALPHA-2 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.pattern\">\n                  ISO 3166-1 ALPHA-2 country code requires 2 letters.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-2 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n              <div class=\"form-group\">\n            <label for=\"iso3\">ISO 3166-1 ALPHA-3 Country Code (3 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso3\"\n                   formControlName=\"iso3\" required\n                   placeholder=\"3 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso3').invalid &&\n                 (form.get('iso3').dirty || form.get('iso3').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso3').errors?.required\">\n                  ISO 3166-1 ALPHA-3 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.pattern\">\n                  ISO 3166-1 ALPHA-3 country code requires 3 letters.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-3 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/countries']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/countries/country-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormBuilder, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { map } from 'rxjs/operators';\nimport { Observable } from 'rxjs';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { Country } from '../countries/country';\nimport { CountryService } from './country.service';\n\n@Component({\n  selector: 'app-country-edit',\n  templateUrl: './country-edit.component.html',\n  styleUrls: ['./country-edit.component.css']\n})\nexport class CountryEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  country: Country;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new country,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  constructor(\n    private fb: FormBuilder,\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private countryService: CountryService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = this.fb.group({\n      name: ['',\n        Validators.required,\n        this.isDupeField(\"name\")\n      ],\n      iso2: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{2}/)\n        ],\n        this.isDupeField(\"iso2\")\n      ],\n      iso3: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{3}/)\n        ],\n        this.isDupeField(\"iso3\")\n      ]\n    });\n\n    this.loadData();\n  }\n\n  loadData() {\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the country from the server\n      this.countryService.get<Country>(this.id)\n        .subscribe(result => {\n          this.country = result;\n          this.title = \"Edit - \" + this.country.name;\n\n          // update the form with the country value\n          this.form.patchValue(this.country);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new Country\";\n    }\n  }\n\n  onSubmit() {\n\n    var country = (this.id) ? this.country : <Country>{};\n\n    country.name = this.form.get(\"name\").value;\n    country.iso2 = this.form.get(\"iso2\").value;\n    country.iso3 = this.form.get(\"iso3\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.countryService\n        .put<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + country.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.countryService\n        .post<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeField(fieldName: string): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n\n      var countryId = (this.id) ? this.id.toString() : \"0\";\n\n      return this.countryService.isDupeField(\n        countryId,\n        fieldName,\n        control.value)\n        .pipe(map(result => {\n          return (result ? { isDupeField: true } : null);\n        }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/countries/country.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CountryService\n    extends BaseService {\n    constructor(\n        http: HttpClient,\n        @Inject('BASE_URL') baseUrl: string) {\n        super(http, baseUrl);\n    }\n\n    getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Countries';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    get<Country>(id): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries/\" + id;\n        return this.http.get<Country>(url);\n    }\n\n    put<Country>(item): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries/\" + item.id;\n        return this.http.put<Country>(url, item);\n    }\n\n    post<Country>(item): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries\";\n        return this.http.post<Country>(url, item);\n    }\n\n    isDupeField(countryId, fieldName, fieldValue): Observable<boolean> {\n        var params = new HttpParams()\n            .set(\"countryId\", countryId)\n            .set(\"fieldName\", fieldName)\n            .set(\"fieldValue\", fieldValue);\n        var url = this.baseUrl + \"api/Countries/IsDupeField\";\n        return this.http.post<boolean>(url, null, { params });\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/countries/country.ts",
    "content": "export interface Country {\n    id: number;\n    name: string;\n    iso2: string;\n    iso3: string;\n    totCities: number;\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">WorldCities</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/cities']\"\n              >Cities</a\n            >\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/countries']\"\n              >Countries</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>WorldCities</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n      <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n</head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/main.ts",
    "content": "import 'hammerjs';\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_09/WorldCities/Controllers/CitiesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CitiesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CitiesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Cities/?pageIndex=0&pageSize=10\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CityDTO>>> GetCities(\n                int pageIndex = 0,\n                int pageSize = 10,\n                string sortColumn = null,\n                string sortOrder = null,\n                string filterColumn = null,\n                string filterQuery = null)\n        {\n            return await ApiResult<CityDTO>.CreateAsync(\n                    _context.Cities\n                        .Select(c => new CityDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            Lat = c.Lat,\n                            Lon = c.Lon,\n                            CountryId = c.Country.Id,\n                            CountryName = c.Country.Name\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        // GET: api/Cities/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<City>> GetCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            return city;\n        }\n\n        // PUT: api/Cities/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCity(int id, City city)\n        {\n            if (id != city.Id)\n            {\n                return BadRequest();\n            }\n\n            //var sourceCity = _context.Cities.Where(i => i.Id == city.Id).FirstOrDefault();\n            //if (sourceCity == null) return BadRequest();\n            //sourceCity.Name = city.Name;\n            //sourceCity.Lat = city.Lat;\n            //sourceCity.Lon = city.Lon;\n\n            _context.Entry(city).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CityExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Cities\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<City>> PostCity(City city)\n        {\n            _context.Cities.Add(city);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCity\", new { id = city.Id }, city);\n        }\n\n        // DELETE: api/Cities/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<City>> DeleteCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            _context.Cities.Remove(city);\n            await _context.SaveChangesAsync();\n\n            return city;\n        }\n\n        private bool CityExists(int id)\n        {\n            return _context.Cities.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeCity\")]\n        public bool IsDupeCity(City city)\n        {\n            return _context.Cities.Any(\n                e => e.Name == city.Name\n                && e.Lat == city.Lat\n                && e.Lon == city.Lon\n                && e.CountryId == city.CountryId\n                && e.Id != city.Id);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Controllers/CountriesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Dynamic.Core;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CountriesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CountriesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Countries/?pageIndex=0&pageSize=10\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CountryDTO>>> GetCountries(\n        int pageIndex = 0,\n        int pageSize = 10,\n        string sortColumn = null,\n        string sortOrder = null,\n        string filterColumn = null,\n        string filterQuery = null)\n        {\n            return await ApiResult<CountryDTO>.CreateAsync(\n                    _context.Countries\n                        .Select(c => new CountryDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            ISO2 = c.ISO2,\n                            ISO3 = c.ISO3,\n                            TotCities = c.Cities.Count\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        //public async Task<ActionResult<ApiResult<dynamic>>> GetCountries(\n        //    int pageIndex = 0,\n        //    int pageSize = 10,\n        //    string sortColumn = null,\n        //    string sortOrder = null,\n        //    string filterColumn = null,\n        //    string filterQuery = null)\n        //{\n        //    return await ApiResult<dynamic>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n\n        //public async Task<ActionResult<ApiResult<Country>>> GetCountries(\n        //int pageIndex = 0,\n        //int pageSize = 10,\n        //string sortColumn = null,\n        //string sortOrder = null,\n        //string filterColumn = null,\n        //string filterQuery = null)\n        //{\n        //    return await ApiResult<Country>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new Country()\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n        // GET: api/Countries/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<Country>> GetCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            return country;\n        }\n\n        // PUT: api/Countries/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCountry(int id, Country country)\n        {\n            if (id != country.Id)\n            {\n                return BadRequest();\n            }\n\n            _context.Entry(country).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CountryExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Countries\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [HttpPost]\n        public async Task<ActionResult<Country>> PostCountry(Country country)\n        {\n            _context.Countries.Add(country);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCountry\", new { id = country.Id }, country);\n        }\n\n        // DELETE: api/Countries/5\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<Country>> DeleteCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            _context.Countries.Remove(country);\n            await _context.SaveChangesAsync();\n\n            return country;\n        }\n\n        private bool CountryExists(int id)\n        {\n            return _context.Countries.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeField\")]\n        public bool IsDupeField(\n            int countryId, \n            string fieldName, \n            string fieldValue)\n        {\n            // Standard approach(using strongly-typed LAMBA expressions)\n            //switch (fieldName)\n            //{\n            //    case \"name\":\n            //        return _context.Countries.Any(\n            //            c => c.Name == fieldValue && c.Id != countryId);\n            //    case \"iso2\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO2 == fieldValue && c.Id != countryId);\n            //    case \"iso3\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO3 == fieldValue && c.Id != countryId);\n            //    default:\n            //        return false;\n            //}\n\n            // Dynamic approach (using System.Linq.Dynamic.Core)\n            return (ApiResult<Country>.IsValidProperty(fieldName, true))\n                ? _context.Countries.Any(\n                    String.Format(\"{0} == @0 && Id != @1\", fieldName),\n                    fieldValue,\n                    countryId)\n                : false;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Controllers/SeedController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing WorldCities.Data;\nusing OfficeOpenXml;\nusing System.IO;\nusing Microsoft.AspNetCore.Hosting;\nusing WorldCities.Data.Models;\nusing System.Text.Json;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]/[action]\")]\n    [ApiController]\n    public class SeedController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n        private readonly IWebHostEnvironment _env;\n\n        public SeedController(\n            ApplicationDbContext context, \n            IWebHostEnvironment env)\n        {\n            _context = context;\n            _env = env;\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> Import()\n        {\n            // NOTE: This method has been updated on 2020.09.13.\n            // The new version is more efficient than the code described in the book's Chapter 4.\n            // ref.: https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/issues/15\n\n            var path = Path.Combine(\n                _env.ContentRootPath,\n                String.Format(\"Data/Source/worldcities.xlsx\"));\n\n            using (var stream = new FileStream(\n                path,\n                FileMode.Open,\n                FileAccess.Read))\n            {\n                using (var ep = new ExcelPackage(stream))\n                {\n                    // get the first worksheet\n\n                    var ws = ep.Workbook.Worksheets[0];\n\n                    // initialize the record counters\n                    var nCountries = 0;\n                    var nCities = 0;\n\n                    #region Import all Countries\n                    // create a list containing all the countries already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCountries = _context.Countries.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n                        var name = row[nRow, 5].GetValue<string>();\n\n                        // does this country already exist in the database?\n                        if (lstCountries.Where(c => c.Name == name).Count() == 0)\n                        {\n                            // create the Country entity and fill it with xlsx data\n                            var country = new Country();\n                            country.Name = name;\n                            country.ISO2 = row[nRow, 6].GetValue<string>();\n                            country.ISO3 = row[nRow, 7].GetValue<string>();\n\n                            // add the new country to the DB context\n                            _context.Countries.Add(country);\n\n                            // store the country to retrieve its Id later on\n                            lstCountries.Add(country);\n\n                            // increment the counter\n                            nCountries++;\n                        }\n                    }\n\n                    // save all the countries into the Database\n                    if (nCountries > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    #region Import all Cities\n                    // create a list containing all the cities already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCities = _context.Cities.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n\n                        var name = row[nRow, 1].GetValue<string>();\n                        var name_ASCII = row[nRow, 2].GetValue<string>();\n                        var countryName = row[nRow, 5].GetValue<string>();\n                        var lat = row[nRow, 3].GetValue<decimal>();\n                        var lon = row[nRow, 4].GetValue<decimal>();\n                        // retrieve country and countryId\n                        var country = lstCountries.Where(c => c.Name == countryName)\n                            .FirstOrDefault();\n                        var countryId = country.Id;\n\n                        // does this city already exist in the database?\n                        if (lstCities.Where(\n                            c => c.Name == name\n                            && c.Lat == lat\n                            && c.Lon == lon\n                            && c.CountryId == countryId\n                        ).Count() == 0)\n                        {\n                            // create the City entity and fill it with xlsx data\n                            var city = new City();\n                            city.Name = name;\n                            city.Name_ASCII = name_ASCII;\n                            city.Lat = lat;\n                            city.Lon = lon;\n                            city.CountryId = countryId;\n\n                            // add the new city to the DB context\n                            _context.Cities.Add(city);\n\n                            // increment the counter\n                            nCities++;\n                        }\n                    }\n\n                    // save all the cities into the Database\n                    if (nCities > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    return new JsonResult(new\n                    {\n                        Cities = nCities,\n                        Countries = nCountries\n                    });\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Chapter_09/WorldCities/Data/ApiResult.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Linq.Dynamic.Core;\nusing System.Reflection;\nusing System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class ApiResult<T>\n    {\n        /// <summary>\n        /// Private constructor called by the CreateAsync method.\n        /// </summary>\n        private ApiResult(\n            List<T> data,\n            int count,\n            int pageIndex,\n            int pageSize,\n            string sortColumn,\n            string sortOrder,\n            string filterColumn,\n            string filterQuery)\n        {\n            Data = data;\n            PageIndex = pageIndex;\n            PageSize = pageSize;\n            TotalCount = count;\n            TotalPages = (int)Math.Ceiling(count / (double)pageSize);\n            SortColumn = sortColumn;\n            SortOrder = sortOrder;\n            FilterColumn = filterColumn;\n            FilterQuery = filterQuery;\n        }\n\n        #region Methods\n        /// <summary>\n        /// Pages, sorts and/or filters a IQueryable source.\n        /// </summary>\n        /// <param name=\"source\">An IQueryable source of generic type</param>\n        /// <param name=\"pageIndex\">Zero-based current page index (0 = first page)</param>\n        /// <param name=\"pageSize\">The actual size of each page</param>\n        /// <param name=\"sortColumn\">The sorting colum name</param>\n        /// <param name=\"sortOrder\">The sorting order (\"ASC\" or \"DESC\")</param>\n        /// <param name=\"filterColumn\">The filtering column name</param>\n        /// <param name=\"filterQuery\">The filtering query (value to lookup)</param>\n        /// <returns>\n        /// A object containing the IQueryable paged/sorted/filtered result \n        /// and all the relevant paging/sorting/filtering navigation info.\n        /// </returns>\n        public static async Task<ApiResult<T>> CreateAsync(\n            IQueryable<T> source,\n            int pageIndex,\n            int pageSize,\n            string sortColumn = null,\n            string sortOrder = null,\n            string filterColumn = null,\n            string filterQuery = null)\n        {\n            if (!String.IsNullOrEmpty(filterColumn)\n                && !String.IsNullOrEmpty(filterQuery)\n                && IsValidProperty(filterColumn))\n            {\n                source = source.Where(\n                    String.Format(\"{0}.Contains(@0)\",\n                    filterColumn),\n                    filterQuery);\n            }\n\n            var count = await source.CountAsync();\n\n            if (!String.IsNullOrEmpty(sortColumn)\n                && IsValidProperty(sortColumn))\n            {\n                sortOrder = !String.IsNullOrEmpty(sortOrder)\n                    && sortOrder.ToUpper() == \"ASC\"\n                    ? \"ASC\"\n                    : \"DESC\";\n                source = source.OrderBy(\n                    String.Format(\n                        \"{0} {1}\",\n                        sortColumn,\n                        sortOrder)\n                    );\n            }\n\n            source = source\n                .Skip(pageIndex * pageSize)\n                .Take(pageSize);\n\n            // retrieve the SQL query (for debug purposes)\n            #if DEBUG\n            {\n                var sql = source.ToSql();\n                // do something with the sql string\n            }\n            #endif\n\n            var data = await source.ToListAsync();\n\n            return new ApiResult<T>(\n                data,\n                count,\n                pageIndex,\n                pageSize,\n                sortColumn,\n                sortOrder,\n                filterColumn,\n                filterQuery);\n        }\n\n        /// <summary>\n        /// Checks if the given property name exists\n        /// to protect against SQL injection attacks\n        /// </summary>\n        public static bool IsValidProperty(\n            string propertyName,\n            bool throwExceptionIfNotFound = true)\n        {\n            var prop = typeof(T).GetProperty(\n                propertyName,\n                BindingFlags.IgnoreCase |\n                BindingFlags.Public |\n                BindingFlags.Static |\n                BindingFlags.Instance);\n            if (prop == null && throwExceptionIfNotFound)\n                throw new NotSupportedException(\n                    String.Format(\n                        \"ERROR: Property '{0}' does not exist.\",\n                        propertyName)\n                    );\n            return prop != null;\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The data result.\n        /// </summary>\n        public List<T> Data { get; private set; }\n\n        /// <summary>\n        /// Zero-based index of current page.\n        /// </summary>\n        public int PageIndex { get; private set; }\n\n        /// <summary>\n        /// Number of items contained in each page.\n        /// </summary>\n        public int PageSize { get; private set; }\n\n        /// <summary>\n        /// Total items count\n        /// </summary>\n        public int TotalCount { get; private set; }\n\n        /// <summary>\n        /// Total pages count\n        /// </summary>\n        public int TotalPages { get; private set; }\n\n        /// <summary>\n        /// TRUE if the current page has a previous page, FALSE otherwise.\n        /// </summary>\n        public bool HasPreviousPage\n        {\n            get\n            {\n                return (PageIndex > 0);\n            }\n        }\n\n        /// <summary>\n        /// TRUE if the current page has a next page, FALSE otherwise.\n        /// </summary>\n        public bool HasNextPage\n        {\n            get\n            {\n                return ((PageIndex +1) < TotalPages);\n            }\n        }\n\n        /// <summary>\n        /// Sorting Column name (or null if none set)\n        /// </summary>\n        public string SortColumn { get; set; }\n\n        /// <summary>\n        /// Sorting Order (\"ASC\", \"DESC\" or null if none set)\n        /// </summary>\n        public string SortOrder { get; set; }\n\n        /// <summary>\n        /// Filter Column name (or null if none set)\n        /// </summary>\n        public string FilterColumn { get; set; }\n\n        /// <summary>\n        /// Filter Query string \n        /// (to be used within the given FilterColumn)\n        /// </summary>\n        public string FilterQuery { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Data/ApplicationDbContext.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Data\n{\n    public class ApplicationDbContext : DbContext\n    {\n        #region Constructor\n        public ApplicationDbContext() : base()\n        {\n        }\n\n        public ApplicationDbContext(DbContextOptions options) : base(options)\n        {\n        }\n        #endregion Constructor\n\n        #region Methods\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            base.OnModelCreating(modelBuilder);\n\n            // Map Entity names to DB Table names\n            modelBuilder.Entity<City>().ToTable(\"Cities\");\n            modelBuilder.Entity<Country>().ToTable(\"Countries\");\n        }\n        #endregion Methods\n\n        #region Properties\n        public DbSet<City> Cities { get; set; }\n        public DbSet<Country> Countries { get; set; }\n        #endregion Properties\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Data/CityDTO.cs",
    "content": "﻿namespace WorldCities.Data\n{\n    public class CityDTO\n    {\n        public CityDTO() { }\n\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        public string Name_ASCII { get; set; }\n\n        public decimal Lat { get; set; }\n\n        public decimal Lon { get; set; }\n\n        public int CountryId { get; set; }\n\n        public string CountryName { get; set; }\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Data/CountryDTO.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class CountryDTO\n    {\n        public CountryDTO() { }\n\n        #region Properties\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n\n        public int TotCities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Data/IQueryableExtensions.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Query;\nusing Microsoft.EntityFrameworkCore.Query.SqlExpressions;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data\n{\n    public static class IQueryableExtension\n    {\n        public static string ToSql<T>(this IQueryable<T> query)\n        {\n            var enumerator = query.Provider\n                .Execute<IEnumerable<T>>(query.Expression).GetEnumerator();\n            var relationalCommandCache = enumerator\n                .Private(\"_relationalCommandCache\");\n            var selectExpression = relationalCommandCache\n                .Private<SelectExpression>(\"_selectExpression\");\n            var factory = relationalCommandCache\n                .Private<IQuerySqlGeneratorFactory>(\"_querySqlGeneratorFactory\");\n\n            var sqlGenerator = factory.Create();\n            var command = sqlGenerator.GetCommand(selectExpression);\n\n            string sql = command.CommandText;\n            return sql;\n        }\n\n        private static object Private(this object obj, string privateField) => \n            obj?.GetType()\n            .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?\n            .GetValue(obj);\n        private static T Private<T>(this object obj, string privateField) => \n            (T)obj?\n            .GetType()\n            .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?\n            .GetValue(obj);\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Data/Migrations/20191123030140_Initial.Designer.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    [Migration(\"20191123030140_Initial\")]\n    partial class Initial\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Data/Migrations/20191123030140_Initial.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace WorldCities.Data.Migrations\n{\n    public partial class Initial : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.CreateTable(\n                name: \"Countries\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    ISO2 = table.Column<string>(nullable: true),\n                    ISO3 = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Countries\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"Cities\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    Name = table.Column<string>(nullable: true),\n                    Name_ASCII = table.Column<string>(nullable: true),\n                    Lat = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    Lon = table.Column<decimal>(type: \"decimal(7,4)\", nullable: false),\n                    CountryId = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_Cities\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_Cities_Countries_CountryId\",\n                        column: x => x.CountryId,\n                        principalTable: \"Countries\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_Cities_CountryId\",\n                table: \"Cities\",\n                column: \"CountryId\");\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.DropTable(\n                name: \"Cities\");\n\n            migrationBuilder.DropTable(\n                name: \"Countries\");\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Data/Migrations/ApplicationDbContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    partial class ApplicationDbContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Data/Models/City.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class City\n    {\n        #region Constructor\n        public City()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this City\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// City name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// City name (in ASCII format)\n        /// </summary>\n        public string Name_ASCII { get; set; }\n\n        /// <summary>\n        /// City latitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lat { get; set; }\n\n        /// <summary>\n        /// City longitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lon { get; set; }\n\n        /// <summary>\n        /// Country Id (foreign key)\n        /// </summary>\n        [ForeignKey(\"Country\")]\n        public int CountryId { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// The country related to this city.\n        /// </summary>\n        public virtual Country Country { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Data/Models/Country.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class Country\n    {\n        #region Constructor\n        public Country()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this Country\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// Country name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-2 format)\n        /// </summary>\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-3 format)\n        /// </summary>\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n        #endregion\n\n        #region Client-side properties\n        /// <summary>\n        /// The number of cities related to this country.\n        /// </summary>\n        [NotMapped]\n        public int TotCities\n        {\n            get\n            {\n                return (Cities != null)\n                    ? Cities.Count\n                    : _TotCities;\n            }\n            set { _TotCities = value; }\n        }\n\n        private int _TotCities = 0;\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// A list containing all the cities related to this country.\n        /// </summary>\n        [JsonIgnore]\n        public virtual List<City> Cities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_09/WorldCities/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Pages/_ViewImports.cshtml",
    "content": "@using WorldCities\n@namespace WorldCities.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_09/WorldCities/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing System.Text.Json;\nusing WorldCities.Data;\n\nnamespace WorldCities\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews()\n                .AddJsonOptions(options => {\n                    // set this option to TRUE to indent the JSON output\n                    options.JsonSerializerOptions.WriteIndented = true;\n                    // set this option to NULL to use PascalCase instead of CamelCase (default)\n                    // options.JsonSerializerOptions.PropertyNamingPolicy = null;\n                });\n\n\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            // Add EntityFramework support for SqlServer.\n            services.AddEntityFrameworkSqlServer();\n\n            // Add ApplicationDbContext.\n            services.AddDbContext<ApplicationDbContext>(options =>\n                options.UseSqlServer(\n                    Configuration.GetConnectionString(\"DefaultConnection\")\n                    )\n            );\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles();\n            }\n\n            app.UseRouting();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/WorldCities.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"EPPlus\" Version=\"4.5.3.2\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Debug\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" Version=\"3.1.0\" />\n    <PackageReference Include=\"System.Linq.Dynamic.Core\" Version=\"1.0.20\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_09/WorldCities/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities/appsettings.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"Server=localhost\\\\SQLEXPRESS;Database=WorldCities;User Id=WorldCities;Password=MyVeryOwn$721;Integrated Security=False;MultipleActiveResultSets=True\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities.Tests/CitiesController_Tests.cs",
    "content": "using Microsoft.EntityFrameworkCore;\nusing WorldCities.Controllers;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\nusing Xunit;\n\nnamespace WorldCities.Tests\n{\n    public class CitiesController_Tests\n    {\n        /// <summary>\n        /// Test the GetCity() method\n        /// </summary>\n        [Fact]\n        public async void GetCity()\n        {\n            #region Arrange\n            var options = new DbContextOptionsBuilder<ApplicationDbContext>()\n                .UseInMemoryDatabase(databaseName: \"WorldCities\")\n                .Options;\n            using (var context = new ApplicationDbContext(options))\n            {\n                context.Add(new City()\n                {\n                    Id = 1,\n                    CountryId = 1,\n                    Lat = 1,\n                    Lon = 1,\n                    Name = \"TestCity1\"\n                });\n                context.SaveChanges();\n            }\n            City city_existing = null;\n            City city_notExisting = null;\n            #endregion\n\n            #region Act\n            using (var context = new ApplicationDbContext(options))\n            {\n                var controller = new CitiesController(context);\n                city_existing = (await controller.GetCity(1)).Value;\n                city_notExisting = (await controller.GetCity(2)).Value;\n            }\n            #endregion\n\n            #region Assert\n            Assert.True(\n                city_existing != null\n                && city_notExisting == null\n                );\n            #endregion\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_09/WorldCities.Tests/WorldCities.Tests.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n\n    <IsPackable>false</IsPackable>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" Version=\"3.1.0\" />\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"16.2.0\" />\n    <PackageReference Include=\"Moq\" Version=\"4.13.1\" />\n    <PackageReference Include=\"xunit\" Version=\"2.4.0\" />\n    <PackageReference Include=\"xunit.runner.visualstudio\" Version=\"2.4.0\" />\n    <PackageReference Include=\"coverlet.collector\" Version=\"1.0.1\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\WorldCities\\WorldCities.csproj\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Chapter_09.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_09\\WorldCities\\WorldCities.csproj\", \"{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities.Tests\", \"Chapter_09\\WorldCities.Tests\\WorldCities.Tests.csproj\", \"{C51AB59B-C825-4F43-9EA0-93EBC0BBD69A}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{C51AB59B-C825-4F43-9EA0-93EBC0BBD69A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{C51AB59B-C825-4F43-9EA0-93EBC0BBD69A}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{C51AB59B-C825-4F43-9EA0-93EBC0BBD69A}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{C51AB59B-C825-4F43-9EA0-93EBC0BBD69A}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_10/AuthSample/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_10/AuthSample/AuthSample.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.ApiAuthorization.IdentityServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.UI\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Relational\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Sqlite\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n  </ItemGroup>\n\n  <ItemGroup>\n    <None Update=\"app.db\" CopyToOutputDirectory=\"PreserveNewest\" ExcludeFromSingleFile=\"true\" />\n  </ItemGroup>\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/README.md",
    "content": "# AuthSample\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"AuthSample\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": false,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\"src/assets\"],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"AuthSample:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"AuthSample:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"AuthSample:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\"src/styles.css\"],\n            \"scripts\": [],\n            \"assets\": [\"src/assets\"]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\"src/tsconfig.app.json\", \"src/tsconfig.spec.json\"],\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"AuthSample-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"AuthSample:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"AuthSample\"\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/package.json",
    "content": "{\n  \"name\": \"authsample\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run AuthSample:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/cdk\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/material\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"hammerjs\": \"2.0.8\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/api-authorization.constants.ts",
    "content": "export const ApplicationName = 'AuthSample';\n\nexport const ReturnUrlType = 'returnUrl';\n\nexport const QueryParameterNames = {\n  ReturnUrl: ReturnUrlType,\n  Message: 'message'\n};\n\nexport const LogoutActions = {\n  LogoutCallback: 'logout-callback',\n  Logout: 'logout',\n  LoggedOut: 'logged-out'\n};\n\nexport const LoginActions = {\n  Login: 'login',\n  LoginCallback: 'login-callback',\n  LoginFailed: 'login-failed',\n  Profile: 'profile',\n  Register: 'register'\n};\n\nlet applicationPaths: ApplicationPathsType = {\n  DefaultLoginRedirectPath: '/',\n  ApiAuthorizationClientConfigurationUrl: `/_configuration/${ApplicationName}`,\n  Login: `authentication/${LoginActions.Login}`,\n  LoginFailed: `authentication/${LoginActions.LoginFailed}`,\n  LoginCallback: `authentication/${LoginActions.LoginCallback}`,\n  Register: `authentication/${LoginActions.Register}`,\n  Profile: `authentication/${LoginActions.Profile}`,\n  LogOut: `authentication/${LogoutActions.Logout}`,\n  LoggedOut: `authentication/${LogoutActions.LoggedOut}`,\n  LogOutCallback: `authentication/${LogoutActions.LogoutCallback}`,\n  LoginPathComponents: [],\n  LoginFailedPathComponents: [],\n  LoginCallbackPathComponents: [],\n  RegisterPathComponents: [],\n  ProfilePathComponents: [],\n  LogOutPathComponents: [],\n  LoggedOutPathComponents: [],\n  LogOutCallbackPathComponents: [],\n  IdentityRegisterPath: '/Identity/Account/Register',\n  IdentityManagePath: '/Identity/Account/Manage'\n};\n\napplicationPaths = {\n  ...applicationPaths,\n  LoginPathComponents: applicationPaths.Login.split('/'),\n  LoginFailedPathComponents: applicationPaths.LoginFailed.split('/'),\n  RegisterPathComponents: applicationPaths.Register.split('/'),\n  ProfilePathComponents: applicationPaths.Profile.split('/'),\n  LogOutPathComponents: applicationPaths.LogOut.split('/'),\n  LoggedOutPathComponents: applicationPaths.LoggedOut.split('/'),\n  LogOutCallbackPathComponents: applicationPaths.LogOutCallback.split('/')\n};\n\ninterface ApplicationPathsType {\n  readonly DefaultLoginRedirectPath: string;\n  readonly ApiAuthorizationClientConfigurationUrl: string;\n  readonly Login: string;\n  readonly LoginFailed: string;\n  readonly LoginCallback: string;\n  readonly Register: string;\n  readonly Profile: string;\n  readonly LogOut: string;\n  readonly LoggedOut: string;\n  readonly LogOutCallback: string;\n  readonly LoginPathComponents: string [];\n  readonly LoginFailedPathComponents: string [];\n  readonly LoginCallbackPathComponents: string [];\n  readonly RegisterPathComponents: string [];\n  readonly ProfilePathComponents: string [];\n  readonly LogOutPathComponents: string [];\n  readonly LoggedOutPathComponents: string [];\n  readonly LogOutCallbackPathComponents: string [];\n  readonly IdentityRegisterPath: string;\n  readonly IdentityManagePath: string;\n}\n\nexport const ApplicationPaths: ApplicationPathsType = applicationPaths;\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/api-authorization.module.spec.ts",
    "content": "import { ApiAuthorizationModule } from './api-authorization.module';\n\ndescribe('ApiAuthorizationModule', () => {\n  let apiAuthorizationModule: ApiAuthorizationModule;\n\n  beforeEach(() => {\n    apiAuthorizationModule = new ApiAuthorizationModule();\n  });\n\n  it('should create an instance', () => {\n    expect(apiAuthorizationModule).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/api-authorization.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { LoginMenuComponent } from './login-menu/login-menu.component';\nimport { LoginComponent } from './login/login.component';\nimport { LogoutComponent } from './logout/logout.component';\nimport { RouterModule } from '@angular/router';\nimport { ApplicationPaths } from './api-authorization.constants';\nimport { HttpClientModule } from '@angular/common/http';\n\n@NgModule({\n  imports: [\n    CommonModule,\n    HttpClientModule,\n    RouterModule.forChild(\n      [\n        { path: ApplicationPaths.Register, component: LoginComponent },\n        { path: ApplicationPaths.Profile, component: LoginComponent },\n        { path: ApplicationPaths.Login, component: LoginComponent },\n        { path: ApplicationPaths.LoginFailed, component: LoginComponent },\n        { path: ApplicationPaths.LoginCallback, component: LoginComponent },\n        { path: ApplicationPaths.LogOut, component: LogoutComponent },\n        { path: ApplicationPaths.LoggedOut, component: LogoutComponent },\n        { path: ApplicationPaths.LogOutCallback, component: LogoutComponent }\n      ]\n    )\n  ],\n  declarations: [LoginMenuComponent, LoginComponent, LogoutComponent],\n  exports: [LoginMenuComponent, LoginComponent, LogoutComponent]\n})\nexport class ApiAuthorizationModule { }\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/authorize.guard.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeGuard } from './authorize.guard';\n\ndescribe('AuthorizeGuard', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeGuard]\n    });\n  });\n\n  it('should ...', inject([AuthorizeGuard], (guard: AuthorizeGuard) => {\n    expect(guard).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/authorize.guard.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { AuthorizeService } from './authorize.service';\nimport { tap } from 'rxjs/operators';\nimport { ApplicationPaths, QueryParameterNames } from './api-authorization.constants';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeGuard implements CanActivate {\n  constructor(private authorize: AuthorizeService, private router: Router) {\n  }\n  canActivate(\n    _next: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {\n      return this.authorize.isAuthenticated()\n        .pipe(tap(isAuthenticated => this.handleAuthorization(isAuthenticated, state)));\n  }\n\n  private handleAuthorization(isAuthenticated: boolean, state: RouterStateSnapshot) {\n    if (!isAuthenticated) {\n      this.router.navigate(ApplicationPaths.LoginPathComponents, {\n        queryParams: {\n          [QueryParameterNames.ReturnUrl]: state.url\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/authorize.interceptor.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeInterceptor } from './authorize.interceptor';\n\ndescribe('AuthorizeInterceptor', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeInterceptor]\n    });\n  });\n\n  it('should be created', inject([AuthorizeInterceptor], (service: AuthorizeInterceptor) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/authorize.interceptor.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';\nimport { Observable } from 'rxjs';\nimport { AuthorizeService } from './authorize.service';\nimport { mergeMap } from 'rxjs/operators';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeInterceptor implements HttpInterceptor {\n  constructor(private authorize: AuthorizeService) { }\n\n  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {\n    return this.authorize.getAccessToken()\n      .pipe(mergeMap(token => this.processRequestWithToken(token, req, next)));\n  }\n\n  // Checks if there is an access_token available in the authorize service\n  // and adds it to the request in case it's targeted at the same origin as the\n  // single page application.\n  private processRequestWithToken(token: string, req: HttpRequest<any>, next: HttpHandler) {\n    if (!!token && this.isSameOriginUrl(req)) {\n      req = req.clone({\n        setHeaders: {\n          Authorization: `Bearer ${token}`\n        }\n      });\n    }\n\n    return next.handle(req);\n  }\n\n  private isSameOriginUrl(req: any) {\n    // It's an absolute url with the same origin.\n    if (req.url.startsWith(`${window.location.origin}/`)) {\n      return true;\n    }\n\n    // It's a protocol relative url with the same origin.\n    // For example: //www.example.com/api/Products\n    if (req.url.startsWith(`//${window.location.host}/`)) {\n      return true;\n    }\n\n    // It's a relative url like /api/Products\n    if (/^\\/[^\\/].*/.test(req.url)) {\n      return true;\n    }\n\n    // It's an absolute or protocol relative url that\n    // doesn't have the same origin.\n    return false;\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/authorize.service.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeService } from './authorize.service';\n\ndescribe('AuthorizeService', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeService]\n    });\n  });\n\n  it('should be created', inject([AuthorizeService], (service: AuthorizeService) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/authorize.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { User, UserManager, WebStorageStateStore } from 'oidc-client';\nimport { BehaviorSubject, concat, from, Observable } from 'rxjs';\nimport { filter, map, mergeMap, take, tap } from 'rxjs/operators';\nimport { ApplicationPaths, ApplicationName } from './api-authorization.constants';\n\nexport type IAuthenticationResult =\n  SuccessAuthenticationResult |\n  FailureAuthenticationResult |\n  RedirectAuthenticationResult;\n\nexport interface SuccessAuthenticationResult {\n  status: AuthenticationResultStatus.Success;\n  state: any;\n}\n\nexport interface FailureAuthenticationResult {\n  status: AuthenticationResultStatus.Fail;\n  message: string;\n}\n\nexport interface RedirectAuthenticationResult {\n  status: AuthenticationResultStatus.Redirect;\n}\n\nexport enum AuthenticationResultStatus {\n  Success,\n  Redirect,\n  Fail\n}\n\nexport interface IUser {\n  name: string;\n}\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeService {\n  // By default pop ups are disabled because they don't work properly on Edge.\n  // If you want to enable pop up authentication simply set this flag to false.\n\n  private popUpDisabled = true;\n  private userManager: UserManager;\n  private userSubject: BehaviorSubject<IUser | null> = new BehaviorSubject(null);\n\n  public isAuthenticated(): Observable<boolean> {\n    return this.getUser().pipe(map(u => !!u));\n  }\n\n  public getUser(): Observable<IUser | null> {\n    return concat(\n      this.userSubject.pipe(take(1), filter(u => !!u)),\n      this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))),\n      this.userSubject.asObservable());\n  }\n\n  public getAccessToken(): Observable<string> {\n    return from(this.ensureUserManagerInitialized())\n      .pipe(mergeMap(() => from(this.userManager.getUser())),\n        map(user => user && user.access_token));\n  }\n\n  // We try to authenticate the user in three different ways:\n  // 1) We try to see if we can authenticate the user silently. This happens\n  //    when the user is already logged in on the IdP and is done using a hidden iframe\n  //    on the client.\n  // 2) We try to authenticate the user using a PopUp Window. This might fail if there is a\n  //    Pop-Up blocker or the user has disabled PopUps.\n  // 3) If the two methods above fail, we redirect the browser to the IdP to perform a traditional\n  //    redirect flow.\n  public async signIn(state: any): Promise<IAuthenticationResult> {\n    await this.ensureUserManagerInitialized();\n    let user: User = null;\n    try {\n      user = await this.userManager.signinSilent(this.createArguments());\n      this.userSubject.next(user.profile);\n      return this.success(state);\n    } catch (silentError) {\n      // User might not be authenticated, fallback to popup authentication\n      console.log('Silent authentication error: ', silentError);\n\n      try {\n        if (this.popUpDisabled) {\n          throw new Error('Popup disabled. Change \\'authorize.service.ts:AuthorizeService.popupDisabled\\' to false to enable it.');\n        }\n        user = await this.userManager.signinPopup(this.createArguments());\n        this.userSubject.next(user.profile);\n        return this.success(state);\n      } catch (popupError) {\n        if (popupError.message === 'Popup window closed') {\n          // The user explicitly cancelled the login action by closing an opened popup.\n          return this.error('The user closed the window.');\n        } else if (!this.popUpDisabled) {\n          console.log('Popup authentication error: ', popupError);\n        }\n\n        // PopUps might be blocked by the user, fallback to redirect\n        try {\n          await this.userManager.signinRedirect(this.createArguments(state));\n          return this.redirect();\n        } catch (redirectError) {\n          console.log('Redirect authentication error: ', redirectError);\n          return this.error(redirectError);\n        }\n      }\n    }\n  }\n\n  public async completeSignIn(url: string): Promise<IAuthenticationResult> {\n    try {\n      await this.ensureUserManagerInitialized();\n      const user = await this.userManager.signinCallback(url);\n      this.userSubject.next(user && user.profile);\n      return this.success(user && user.state);\n    } catch (error) {\n      console.log('There was an error signing in: ', error);\n      return this.error('There was an error signing in.');\n    }\n  }\n\n  public async signOut(state: any): Promise<IAuthenticationResult> {\n    try {\n      if (this.popUpDisabled) {\n        throw new Error('Popup disabled. Change \\'authorize.service.ts:AuthorizeService.popupDisabled\\' to false to enable it.');\n      }\n\n      await this.ensureUserManagerInitialized();\n      await this.userManager.signoutPopup(this.createArguments());\n      this.userSubject.next(null);\n      return this.success(state);\n    } catch (popupSignOutError) {\n      console.log('Popup signout error: ', popupSignOutError);\n      try {\n        await this.userManager.signoutRedirect(this.createArguments(state));\n        return this.redirect();\n      } catch (redirectSignOutError) {\n        console.log('Redirect signout error: ', popupSignOutError);\n        return this.error(redirectSignOutError);\n      }\n    }\n  }\n\n  public async completeSignOut(url: string): Promise<IAuthenticationResult> {\n    await this.ensureUserManagerInitialized();\n    try {\n      const state = await this.userManager.signoutCallback(url);\n      this.userSubject.next(null);\n      return this.success(state && state.data);\n    } catch (error) {\n      console.log(`There was an error trying to log out '${error}'.`);\n      return this.error(error);\n    }\n  }\n\n  private createArguments(state?: any): any {\n    return { useReplaceToNavigate: true, data: state };\n  }\n\n  private error(message: string): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Fail, message };\n  }\n\n  private success(state: any): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Success, state };\n  }\n\n  private redirect(): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Redirect };\n  }\n\n  private async ensureUserManagerInitialized(): Promise<void> {\n    if (this.userManager !== undefined) {\n      return;\n    }\n\n    const response = await fetch(ApplicationPaths.ApiAuthorizationClientConfigurationUrl);\n    if (!response.ok) {\n      throw new Error(`Could not load settings for '${ApplicationName}'`);\n    }\n\n    const settings: any = await response.json();\n    settings.automaticSilentRenew = true;\n    settings.includeIdTokenInSilentRenew = true;\n    this.userManager = new UserManager(settings);\n\n    this.userManager.events.addUserSignedOut(async () => {\n      await this.userManager.removeUser();\n      this.userSubject.next(null);\n    });\n  }\n\n  private getUserFromStorage(): Observable<IUser> {\n    return from(this.ensureUserManagerInitialized())\n      .pipe(\n        mergeMap(() => this.userManager.getUser()),\n        map(u => u && u.profile));\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/login/login.component.css",
    "content": ""
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/login/login.component.html",
    "content": "<p>{{ message | async }}</p>\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/login/login.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LoginComponent } from './login.component';\n\ndescribe('LoginComponent', () => {\n  let component: LoginComponent;\n  let fixture: ComponentFixture<LoginComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LoginComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LoginComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/login/login.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthorizeService, AuthenticationResultStatus } from '../authorize.service';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { BehaviorSubject } from 'rxjs';\nimport { LoginActions, QueryParameterNames, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants';\n\n// The main responsibility of this component is to handle the user's login process.\n// This is the starting point for the login process. Any component that needs to authenticate\n// a user can simply perform a redirect to this component with a returnUrl query parameter and\n// let the component perform the login and return back to the return url.\n@Component({\n  selector: 'app-login',\n  templateUrl: './login.component.html',\n  styleUrls: ['./login.component.css']\n})\nexport class LoginComponent implements OnInit {\n  public message = new BehaviorSubject<string>(null);\n\n  constructor(\n    private authorizeService: AuthorizeService,\n    private activatedRoute: ActivatedRoute,\n    private router: Router) { }\n\n  async ngOnInit() {\n    const action = this.activatedRoute.snapshot.url[1];\n    switch (action.path) {\n      case LoginActions.Login:\n        await this.login(this.getReturnUrl());\n        break;\n      case LoginActions.LoginCallback:\n        await this.processLoginCallback();\n        break;\n      case LoginActions.LoginFailed:\n        const message = this.activatedRoute.snapshot.queryParamMap.get(QueryParameterNames.Message);\n        this.message.next(message);\n        break;\n      case LoginActions.Profile:\n        this.redirectToProfile();\n        break;\n      case LoginActions.Register:\n        this.redirectToRegister();\n        break;\n      default:\n        throw new Error(`Invalid action '${action}'`);\n    }\n  }\n\n\n  private async login(returnUrl: string): Promise<void> {\n    const state: INavigationState = { returnUrl };\n    const result = await this.authorizeService.signIn(state);\n    this.message.next(undefined);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        break;\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(returnUrl);\n        break;\n      case AuthenticationResultStatus.Fail:\n        await this.router.navigate(ApplicationPaths.LoginFailedPathComponents, {\n          queryParams: { [QueryParameterNames.Message]: result.message }\n        });\n        break;\n      default:\n        throw new Error(`Invalid status result ${(result as any).status}.`);\n    }\n  }\n\n  private async processLoginCallback(): Promise<void> {\n    const url = window.location.href;\n    const result = await this.authorizeService.completeSignIn(url);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        // There should not be any redirects as completeSignIn never redirects.\n        throw new Error('Should not redirect.');\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(this.getReturnUrl(result.state));\n        break;\n      case AuthenticationResultStatus.Fail:\n        this.message.next(result.message);\n        break;\n    }\n  }\n\n  private redirectToRegister(): any {\n    this.redirectToApiAuthorizationPath(\n      `${ApplicationPaths.IdentityRegisterPath}?returnUrl=${encodeURI('/' + ApplicationPaths.Login)}`);\n  }\n\n  private redirectToProfile(): void {\n    this.redirectToApiAuthorizationPath(ApplicationPaths.IdentityManagePath);\n  }\n\n  private async navigateToReturnUrl(returnUrl: string) {\n    // It's important that we do a replace here so that we remove the callback uri with the\n    // fragment containing the tokens from the browser history.\n    await this.router.navigateByUrl(returnUrl, {\n      replaceUrl: true\n    });\n  }\n\n  private getReturnUrl(state?: INavigationState): string {\n    const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl;\n    // If the url is comming from the query string, check that is either\n    // a relative url or an absolute url\n    if (fromQuery &&\n      !(fromQuery.startsWith(`${window.location.origin}/`) ||\n        /\\/[^\\/].*/.test(fromQuery))) {\n      // This is an extra check to prevent open redirects.\n      throw new Error('Invalid return url. The return url needs to have the same origin as the current page.');\n    }\n    return (state && state.returnUrl) ||\n      fromQuery ||\n      ApplicationPaths.DefaultLoginRedirectPath;\n  }\n\n  private redirectToApiAuthorizationPath(apiAuthorizationPath: string) {\n    // It's important that we do a replace here so that when the user hits the back arrow on the\n    // browser they get sent back to where it was on the app instead of to an endpoint on this\n    // component.\n    const redirectUrl = `${window.location.origin}${apiAuthorizationPath}`;\n    window.location.replace(redirectUrl);\n  }\n}\n\ninterface INavigationState {\n  [ReturnUrlType]: string;\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/login-menu/login-menu.component.css",
    "content": ""
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/login-menu/login-menu.component.html",
    "content": "<ul class=\"navbar-nav\" *ngIf=\"isAuthenticated | async\">\n    <li class=\"nav-item\">\n        <a  class=\"nav-link text-dark\" [routerLink]='[\"/authentication/profile\"]' title=\"Manage\">Hello {{ userName | async }}</a>\n    </li>\n    <li class=\"nav-item\">\n        <a  class=\"nav-link text-dark\" [routerLink]='[\"/authentication/logout\"]' [state]='{ local: true }' title=\"Logout\">Logout</a>\n    </li>\n</ul>\n<ul class=\"navbar-nav\" *ngIf=\"!(isAuthenticated | async)\">\n  <li class=\"nav-item\">\n        <a class=\"nav-link text-dark\" [routerLink]='[\"/authentication/register\"]'>Register</a>\n    </li>\n    <li class=\"nav-item\">\n        <a class=\"nav-link text-dark\" [routerLink]='[\"/authentication/login\"]'>Login</a>\n    </li>\n</ul>\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/login-menu/login-menu.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LoginMenuComponent } from './login-menu.component';\n\ndescribe('LoginMenuComponent', () => {\n  let component: LoginMenuComponent;\n  let fixture: ComponentFixture<LoginMenuComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LoginMenuComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LoginMenuComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/login-menu/login-menu.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthorizeService } from '../authorize.service';\nimport { Observable } from 'rxjs';\nimport { map, tap } from 'rxjs/operators';\n\n@Component({\n  selector: 'app-login-menu',\n  templateUrl: './login-menu.component.html',\n  styleUrls: ['./login-menu.component.css']\n})\nexport class LoginMenuComponent implements OnInit {\n  public isAuthenticated: Observable<boolean>;\n  public userName: Observable<string>;\n\n  constructor(private authorizeService: AuthorizeService) { }\n\n  ngOnInit() {\n    this.isAuthenticated = this.authorizeService.isAuthenticated();\n    this.userName = this.authorizeService.getUser().pipe(map(u => u && u.name));\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/logout/logout.component.css",
    "content": ""
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/logout/logout.component.html",
    "content": "<p>{{ message | async }}</p>"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/logout/logout.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LogoutComponent } from './logout.component';\n\ndescribe('LogoutComponent', () => {\n  let component: LogoutComponent;\n  let fixture: ComponentFixture<LogoutComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LogoutComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LogoutComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/api-authorization/logout/logout.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthenticationResultStatus, AuthorizeService } from '../authorize.service';\nimport { BehaviorSubject } from 'rxjs';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { take } from 'rxjs/operators';\nimport { LogoutActions, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants';\n\n// The main responsibility of this component is to handle the user's logout process.\n// This is the starting point for the logout process, which is usually initiated when a\n// user clicks on the logout button on the LoginMenu component.\n@Component({\n  selector: 'app-logout',\n  templateUrl: './logout.component.html',\n  styleUrls: ['./logout.component.css']\n})\nexport class LogoutComponent implements OnInit {\n  public message = new BehaviorSubject<string>(null);\n\n  constructor(\n    private authorizeService: AuthorizeService,\n    private activatedRoute: ActivatedRoute,\n    private router: Router) { }\n\n  async ngOnInit() {\n    const action = this.activatedRoute.snapshot.url[1];\n    switch (action.path) {\n      case LogoutActions.Logout:\n        if (!!window.history.state.local) {\n          await this.logout(this.getReturnUrl());\n        } else {\n          // This prevents regular links to <app>/authentication/logout from triggering a logout\n          this.message.next('The logout was not initiated from within the page.');\n        }\n\n        break;\n      case LogoutActions.LogoutCallback:\n        await this.processLogoutCallback();\n        break;\n      case LogoutActions.LoggedOut:\n        this.message.next('You successfully logged out!');\n        break;\n      default:\n        throw new Error(`Invalid action '${action}'`);\n    }\n  }\n\n  private async logout(returnUrl: string): Promise<void> {\n    const state: INavigationState = { returnUrl };\n    const isauthenticated = await this.authorizeService.isAuthenticated().pipe(\n      take(1)\n    ).toPromise();\n    if (isauthenticated) {\n      const result = await this.authorizeService.signOut(state);\n      switch (result.status) {\n        case AuthenticationResultStatus.Redirect:\n          break;\n        case AuthenticationResultStatus.Success:\n          await this.navigateToReturnUrl(returnUrl);\n          break;\n        case AuthenticationResultStatus.Fail:\n          this.message.next(result.message);\n          break;\n        default:\n          throw new Error('Invalid authentication result status.');\n      }\n    } else {\n      this.message.next('You successfully logged out!');\n    }\n  }\n\n  private async processLogoutCallback(): Promise<void> {\n    const url = window.location.href;\n    const result = await this.authorizeService.completeSignOut(url);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        // There should not be any redirects as the only time completeAuthentication finishes\n        // is when we are doing a redirect sign in flow.\n        throw new Error('Should not redirect.');\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(this.getReturnUrl(result.state));\n        break;\n      case AuthenticationResultStatus.Fail:\n        this.message.next(result.message);\n        break;\n      default:\n        throw new Error('Invalid authentication result status.');\n    }\n  }\n\n  private async navigateToReturnUrl(returnUrl: string) {\n    await this.router.navigateByUrl(returnUrl, {\n      replaceUrl: true\n    });\n  }\n\n  private getReturnUrl(state?: INavigationState): string {\n    const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl;\n    // If the url is comming from the query string, check that is either\n    // a relative url or an absolute url\n    if (fromQuery &&\n      !(fromQuery.startsWith(`${window.location.origin}/`) ||\n        /\\/[^\\/].*/.test(fromQuery))) {\n      // This is an extra check to prevent open redirects.\n      throw new Error('Invalid return url. The return url needs to have the same origin as the current page.');\n    }\n    return (state && state.returnUrl) ||\n      fromQuery ||\n      ApplicationPaths.LoggedOut;\n  }\n}\n\ninterface INavigationState {\n  [ReturnUrlType]: string;\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/app.component.html",
    "content": "<body>\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { CounterComponent } from './counter/counter.component';\nimport { FetchDataComponent } from './fetch-data/fetch-data.component';\nimport { ApiAuthorizationModule } from 'src/api-authorization/api-authorization.module';\nimport { AuthorizeGuard } from 'src/api-authorization/authorize.guard';\nimport { AuthorizeInterceptor } from 'src/api-authorization/authorize.interceptor';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavMenuComponent,\n    HomeComponent,\n    CounterComponent,\n    FetchDataComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    ApiAuthorizationModule,\n    RouterModule.forRoot([\n      { path: '', component: HomeComponent, pathMatch: 'full' },\n      { path: 'counter', component: CounterComponent },\n      { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },\n    ])\n  ],\n  providers: [\n    { provide: HTTP_INTERCEPTORS, useClass: AuthorizeInterceptor, multi: true }\n  ],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/counter/counter.component.html",
    "content": "<h1>Counter</h1>\n\n<p>This is a simple example of an Angular component.</p>\n\n<p aria-live=\"polite\">Current count: <strong>{{ currentCount }}</strong></p>\n\n<button class=\"btn btn-primary\" (click)=\"incrementCounter()\">Increment</button>\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/counter/counter.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { CounterComponent } from './counter.component';\n\ndescribe('CounterComponent', () => {\n  let component: CounterComponent;\n  let fixture: ComponentFixture<CounterComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ CounterComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(CounterComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should display a title', async(() => {\n    const titleText = fixture.nativeElement.querySelector('h1').textContent;\n    expect(titleText).toEqual('Counter');\n  }));\n\n  it('should start with count 0, then increments by 1 when clicked', async(() => {\n    const countElement = fixture.nativeElement.querySelector('strong');\n    expect(countElement.textContent).toEqual('0');\n\n    const incrementButton = fixture.nativeElement.querySelector('button');\n    incrementButton.click();\n    fixture.detectChanges();\n    expect(countElement.textContent).toEqual('1');\n  }));\n});\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/counter/counter.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-counter-component',\n  templateUrl: './counter.component.html'\n})\nexport class CounterComponent {\n  public currentCount = 0;\n\n  public incrementCounter() {\n    this.currentCount++;\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/fetch-data/fetch-data.component.html",
    "content": "<h1 id=\"tableLabel\">Weather forecast</h1>\n\n<p>This component demonstrates fetching data from the server.</p>\n\n<p *ngIf=\"!forecasts\"><em>Loading...</em></p>\n\n<table class='table table-striped' aria-labelledby=\"tableLabel\" *ngIf=\"forecasts\">\n  <thead>\n    <tr>\n      <th>Date</th>\n      <th>Temp. (C)</th>\n      <th>Temp. (F)</th>\n      <th>Summary</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr *ngFor=\"let forecast of forecasts\">\n      <td>{{ forecast.date }}</td>\n      <td>{{ forecast.temperatureC }}</td>\n      <td>{{ forecast.temperatureF }}</td>\n      <td>{{ forecast.summary }}</td>\n    </tr>\n  </tbody>\n</table>\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/fetch-data/fetch-data.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\n\n@Component({\n  selector: 'app-fetch-data',\n  templateUrl: './fetch-data.component.html'\n})\nexport class FetchDataComponent {\n  public forecasts: WeatherForecast[];\n\n  constructor(http: HttpClient, @Inject('BASE_URL') baseUrl: string) {\n    http.get<WeatherForecast[]>(baseUrl + 'weatherforecast').subscribe(result => {\n      this.forecasts = result;\n    }, error => console.error(error));\n  }\n}\n\ninterface WeatherForecast {\n  date: string;\n  temperatureC: number;\n  temperatureF: number;\n  summary: string;\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">AuthSample</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <app-login-menu></app-login-menu>\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/counter']\"\n              >Counter</a\n            >\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/fetch-data']\"\n              >Fetch data</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>AuthSample</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n  </head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/Controllers/OidcConfigurationController.cs",
    "content": "﻿using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Extensions.Logging;\n\nnamespace AuthSample.Controllers\n{\n    public class OidcConfigurationController : Controller\n    {\n        private readonly ILogger<OidcConfigurationController> logger;\n\n        public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger<OidcConfigurationController> _logger)\n        {\n            ClientRequestParametersProvider = clientRequestParametersProvider;\n            logger = _logger;\n        }\n\n        public IClientRequestParametersProvider ClientRequestParametersProvider { get; }\n\n        [HttpGet(\"_configuration/{clientId}\")]\n        public IActionResult GetClientRequestParameters([FromRoute]string clientId)\n        {\n            var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);\n            return Ok(parameters);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/Controllers/WeatherForecastController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Extensions.Logging;\n\nnamespace AuthSample.Controllers\n{\n    [Authorize]\n    [ApiController]\n    [Route(\"[controller]\")]\n    public class WeatherForecastController : ControllerBase\n    {\n        private static readonly string[] Summaries = new[]\n        {\n            \"Freezing\", \"Bracing\", \"Chilly\", \"Cool\", \"Mild\", \"Warm\", \"Balmy\", \"Hot\", \"Sweltering\", \"Scorching\"\n        };\n\n        private readonly ILogger<WeatherForecastController> _logger;\n\n        public WeatherForecastController(ILogger<WeatherForecastController> logger)\n        {\n            _logger = logger;\n        }\n\n        [HttpGet]\n        public IEnumerable<WeatherForecast> Get()\n        {\n            var rng = new Random();\n            return Enumerable.Range(1, 5).Select(index => new WeatherForecast\n            {\n                Date = DateTime.Now.AddDays(index),\n                TemperatureC = rng.Next(-20, 55),\n                Summary = Summaries[rng.Next(Summaries.Length)]\n            })\n            .ToArray();\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/Data/ApplicationDbContext.cs",
    "content": "﻿using AuthSample.Models;\nusing IdentityServer4.EntityFramework.Options;\nusing Microsoft.AspNetCore.ApiAuthorization.IdentityServer;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Options;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace AuthSample.Data\n{\n    public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>\n    {\n        public ApplicationDbContext(\n            DbContextOptions options,\n            IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)\n        {\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing AuthSample.Data;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\n\nnamespace AuthSample.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    [Migration(\"00000000000000_CreateIdentitySchema\")]\n    partial class CreateIdentitySchema\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0-rc1.19455.8\");\n\n            modelBuilder.Entity(\"AuthSample.Models.ApplicationUser\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<int>(\"AccessFailedCount\")\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"Email\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.Property<bool>(\"EmailConfirmed\")\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<bool>(\"LockoutEnabled\")\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<DateTimeOffset?>(\"LockoutEnd\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"NormalizedEmail\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedUserName\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"PasswordHash\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"PhoneNumber\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<bool>(\"PhoneNumberConfirmed\")\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<string>(\"SecurityStamp\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<bool>(\"TwoFactorEnabled\")\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<string>(\"UserName\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedEmail\")\n                        .HasName(\"EmailIndex\");\n\n                    b.HasIndex(\"NormalizedUserName\")\n                        .IsUnique()\n                        .HasName(\"UserNameIndex\");\n\n                    b.ToTable(\"AspNetUsers\");\n                });\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.DeviceFlowCodes\", b =>\n                {\n                    b.Property<string>(\"UserCode\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(50000);\n\n                    b.Property<string>(\"DeviceCode\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.HasKey(\"UserCode\");\n\n                    b.HasIndex(\"DeviceCode\")\n                        .IsUnique();\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.ToTable(\"DeviceCodes\");\n                });\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.PersistedGrant\", b =>\n                {\n                    b.Property<string>(\"Key\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(50000);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(50);\n\n                    b.HasKey(\"Key\");\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.HasIndex(\"SubjectId\", \"ClientId\", \"Type\");\n\n                    b.ToTable(\"PersistedGrants\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRole\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedName\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedName\")\n                        .IsUnique()\n                        .HasName(\"RoleNameIndex\");\n\n                    b.ToTable(\"AspNetRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"RoleId\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetRoleClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderKey\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderDisplayName\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\");\n\n                    b.HasKey(\"LoginProvider\", \"ProviderKey\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserLogins\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"RoleId\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.HasKey(\"UserId\", \"RoleId\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetUserRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Value\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.HasKey(\"UserId\", \"LoginProvider\", \"Name\");\n\n                    b.ToTable(\"AspNetUserTokens\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.HasOne(\"AuthSample.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.HasOne(\"AuthSample.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"AuthSample.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.HasOne(\"AuthSample.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/Data/Migrations/00000000000000_CreateIdentitySchema.cs",
    "content": "﻿using System;\nusing Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace AuthSample.Data.Migrations\n{\n    public partial class CreateIdentitySchema : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.CreateTable(\n                name: \"AspNetRoles\",\n                columns: table => new\n                {\n                    Id = table.Column<string>(nullable: false),\n                    Name = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedName = table.Column<string>(maxLength: 256, nullable: true),\n                    ConcurrencyStamp = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetRoles\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUsers\",\n                columns: table => new\n                {\n                    Id = table.Column<string>(nullable: false),\n                    UserName = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),\n                    Email = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),\n                    EmailConfirmed = table.Column<bool>(nullable: false),\n                    PasswordHash = table.Column<string>(nullable: true),\n                    SecurityStamp = table.Column<string>(nullable: true),\n                    ConcurrencyStamp = table.Column<string>(nullable: true),\n                    PhoneNumber = table.Column<string>(nullable: true),\n                    PhoneNumberConfirmed = table.Column<bool>(nullable: false),\n                    TwoFactorEnabled = table.Column<bool>(nullable: false),\n                    LockoutEnd = table.Column<DateTimeOffset>(nullable: true),\n                    LockoutEnabled = table.Column<bool>(nullable: false),\n                    AccessFailedCount = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUsers\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"DeviceCodes\",\n                columns: table => new\n                {\n                    UserCode = table.Column<string>(maxLength: 200, nullable: false),\n                    DeviceCode = table.Column<string>(maxLength: 200, nullable: false),\n                    SubjectId = table.Column<string>(maxLength: 200, nullable: true),\n                    ClientId = table.Column<string>(maxLength: 200, nullable: false),\n                    CreationTime = table.Column<DateTime>(nullable: false),\n                    Expiration = table.Column<DateTime>(nullable: false),\n                    Data = table.Column<string>(maxLength: 50000, nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_DeviceCodes\", x => x.UserCode);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"PersistedGrants\",\n                columns: table => new\n                {\n                    Key = table.Column<string>(maxLength: 200, nullable: false),\n                    Type = table.Column<string>(maxLength: 50, nullable: false),\n                    SubjectId = table.Column<string>(maxLength: 200, nullable: true),\n                    ClientId = table.Column<string>(maxLength: 200, nullable: false),\n                    CreationTime = table.Column<DateTime>(nullable: false),\n                    Expiration = table.Column<DateTime>(nullable: true),\n                    Data = table.Column<string>(maxLength: 50000, nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_PersistedGrants\", x => x.Key);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetRoleClaims\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"Sqlite:Autoincrement\", true),\n                    RoleId = table.Column<string>(nullable: false),\n                    ClaimType = table.Column<string>(nullable: true),\n                    ClaimValue = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetRoleClaims\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_AspNetRoleClaims_AspNetRoles_RoleId\",\n                        column: x => x.RoleId,\n                        principalTable: \"AspNetRoles\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserClaims\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"Sqlite:Autoincrement\", true),\n                    UserId = table.Column<string>(nullable: false),\n                    ClaimType = table.Column<string>(nullable: true),\n                    ClaimValue = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserClaims\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserClaims_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserLogins\",\n                columns: table => new\n                {\n                    LoginProvider = table.Column<string>(maxLength: 128, nullable: false),\n                    ProviderKey = table.Column<string>(maxLength: 128, nullable: false),\n                    ProviderDisplayName = table.Column<string>(nullable: true),\n                    UserId = table.Column<string>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserLogins\", x => new { x.LoginProvider, x.ProviderKey });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserLogins_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserRoles\",\n                columns: table => new\n                {\n                    UserId = table.Column<string>(nullable: false),\n                    RoleId = table.Column<string>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserRoles\", x => new { x.UserId, x.RoleId });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserRoles_AspNetRoles_RoleId\",\n                        column: x => x.RoleId,\n                        principalTable: \"AspNetRoles\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserRoles_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserTokens\",\n                columns: table => new\n                {\n                    UserId = table.Column<string>(nullable: false),\n                    LoginProvider = table.Column<string>(maxLength: 128, nullable: false),\n                    Name = table.Column<string>(maxLength: 128, nullable: false),\n                    Value = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserTokens\", x => new { x.UserId, x.LoginProvider, x.Name });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserTokens_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetRoleClaims_RoleId\",\n                table: \"AspNetRoleClaims\",\n                column: \"RoleId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"RoleNameIndex\",\n                table: \"AspNetRoles\",\n                column: \"NormalizedName\",\n                unique: true);\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserClaims_UserId\",\n                table: \"AspNetUserClaims\",\n                column: \"UserId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserLogins_UserId\",\n                table: \"AspNetUserLogins\",\n                column: \"UserId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserRoles_RoleId\",\n                table: \"AspNetUserRoles\",\n                column: \"RoleId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"EmailIndex\",\n                table: \"AspNetUsers\",\n                column: \"NormalizedEmail\");\n\n            migrationBuilder.CreateIndex(\n                name: \"UserNameIndex\",\n                table: \"AspNetUsers\",\n                column: \"NormalizedUserName\",\n                unique: true);\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_DeviceCodes_DeviceCode\",\n                table: \"DeviceCodes\",\n                column: \"DeviceCode\",\n                unique: true);\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_DeviceCodes_Expiration\",\n                table: \"DeviceCodes\",\n                column: \"Expiration\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_PersistedGrants_Expiration\",\n                table: \"PersistedGrants\",\n                column: \"Expiration\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_PersistedGrants_SubjectId_ClientId_Type\",\n                table: \"PersistedGrants\",\n                columns: new[] { \"SubjectId\", \"ClientId\", \"Type\" });\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.DropTable(\n                name: \"AspNetRoleClaims\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserClaims\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserLogins\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserRoles\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserTokens\");\n\n            migrationBuilder.DropTable(\n                name: \"DeviceCodes\");\n\n            migrationBuilder.DropTable(\n                name: \"PersistedGrants\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetRoles\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUsers\");\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/Data/Migrations/ApplicationDbContextModelSnapshot.cs",
    "content": "// <auto-generated />\nusing System;\nusing AuthSample.Data;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\n\nnamespace AuthSample.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    partial class ApplicationDbContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.0.0-rc1.19455.8\");\n\n            modelBuilder.Entity(\"AuthSample.Models.ApplicationUser\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<int>(\"AccessFailedCount\")\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"Email\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.Property<bool>(\"EmailConfirmed\")\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<bool>(\"LockoutEnabled\")\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<DateTimeOffset?>(\"LockoutEnd\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"NormalizedEmail\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedUserName\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"PasswordHash\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"PhoneNumber\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<bool>(\"PhoneNumberConfirmed\")\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<string>(\"SecurityStamp\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<bool>(\"TwoFactorEnabled\")\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<string>(\"UserName\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedEmail\")\n                        .HasName(\"EmailIndex\");\n\n                    b.HasIndex(\"NormalizedUserName\")\n                        .IsUnique()\n                        .HasName(\"UserNameIndex\");\n\n                    b.ToTable(\"AspNetUsers\");\n                });\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.DeviceFlowCodes\", b =>\n                {\n                    b.Property<string>(\"UserCode\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(50000);\n\n                    b.Property<string>(\"DeviceCode\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.HasKey(\"UserCode\");\n\n                    b.HasIndex(\"DeviceCode\")\n                        .IsUnique();\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.ToTable(\"DeviceCodes\");\n                });\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.PersistedGrant\", b =>\n                {\n                    b.Property<string>(\"Key\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(50000);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(50);\n\n                    b.HasKey(\"Key\");\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.HasIndex(\"SubjectId\", \"ClientId\", \"Type\");\n\n                    b.ToTable(\"PersistedGrants\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRole\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedName\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedName\")\n                        .IsUnique()\n                        .HasName(\"RoleNameIndex\");\n\n                    b.ToTable(\"AspNetRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"RoleId\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetRoleClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"INTEGER\");\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderKey\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderDisplayName\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"TEXT\");\n\n                    b.HasKey(\"LoginProvider\", \"ProviderKey\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserLogins\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"RoleId\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.HasKey(\"UserId\", \"RoleId\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetUserRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"TEXT\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Value\")\n                        .HasColumnType(\"TEXT\");\n\n                    b.HasKey(\"UserId\", \"LoginProvider\", \"Name\");\n\n                    b.ToTable(\"AspNetUserTokens\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.HasOne(\"AuthSample.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.HasOne(\"AuthSample.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"AuthSample.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.HasOne(\"AuthSample.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/Models/ApplicationUser.cs",
    "content": "﻿using Microsoft.AspNetCore.Identity;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace AuthSample.Models\n{\n    public class ApplicationUser : IdentityUser\n    {\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_10/AuthSample/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace AuthSample.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/Pages/Shared/_LoginPartial.cshtml",
    "content": "﻿@using Microsoft.AspNetCore.Identity\n@using AuthSample.Models;\n@inject SignInManager<ApplicationUser> SignInManager\n@inject UserManager<ApplicationUser> UserManager\n\n@{\n    string returnUrl = null;\n    var query = ViewContext.HttpContext.Request.Query;\n    if (query.ContainsKey(\"returnUrl\"))\n    {\n        returnUrl = query[\"returnUrl\"];\n    }\n}\n\n<ul class=\"navbar-nav\">\n    @if (SignInManager.IsSignedIn(User))\n    {\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Manage/Index\" title=\"Manage\">Hello @User.Identity.Name!</a>\n        </li>\n        <li class=\"nav-item\">\n            <form class=\"form-inline\" asp-area=\"Identity\" asp-page=\"/Account/Logout\" asp-route-returnUrl=\"/\">\n                <button type=\"submit\" class=\"nav-link btn btn-link text-dark\">Logout</button>\n            </form>\n        </li>\n    }\n    else\n    {\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Register\" asp-route-returnUrl=\"@returnUrl\">Register</a>\n        </li>\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Login\" asp-route-returnUrl=\"@returnUrl\">Login</a>\n        </li>\n    }\n</ul>\n"
  },
  {
    "path": "Chapter_10/AuthSample/Pages/_ViewImports.cshtml",
    "content": "@using AuthSample\n@namespace AuthSample.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_10/AuthSample/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Hosting;\nusing Microsoft.Extensions.Logging;\n\nnamespace AuthSample\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateHostBuilder(args).Build().Run();\n        }\n\n        public static IHostBuilder CreateHostBuilder(string[] args) =>\n            Host.CreateDefaultBuilder(args)\n                .ConfigureWebHostDefaults(webBuilder =>\n                {\n                    webBuilder.UseStartup<Startup>();\n                });\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/Startup.cs",
    "content": "using Microsoft.AspNetCore.Authentication;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Identity.UI;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.EntityFrameworkCore;\nusing AuthSample.Data;\nusing AuthSample.Models;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace AuthSample\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddDbContext<ApplicationDbContext>(options =>\n                options.UseSqlite(\n                    Configuration.GetConnectionString(\"DefaultConnection\")));\n\n            services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)\n                .AddEntityFrameworkStores<ApplicationDbContext>();\n\n            services.AddIdentityServer()\n                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();\n\n            services.AddAuthentication()\n                .AddIdentityServerJwt();\n            services.AddControllersWithViews();\n            services.AddRazorPages();\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n                app.UseDatabaseErrorPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles();\n            }\n\n            app.UseRouting();\n\n            app.UseAuthentication();\n            app.UseIdentityServer();\n            app.UseAuthorization();\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n                endpoints.MapRazorPages();\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/WeatherForecast.cs",
    "content": "using System;\n\nnamespace AuthSample\n{\n    public class WeatherForecast\n    {\n        public DateTime Date { get; set; }\n\n        public int TemperatureC { get; set; }\n\n        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);\n\n        public string Summary { get; set; }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft\": \"Warning\",\n      \"Microsoft.Hosting.Lifetime\": \"Information\"\n    }\n  },\n  \"IdentityServer\": {\n    \"Key\": {\n      \"Type\": \"Development\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_10/AuthSample/appsettings.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"DataSource=app.db\"\n  },\n  \"Logging\": {\n      \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft\": \"Warning\",\n      \"Microsoft.Hosting.Lifetime\": \"Information\"\n      }\n    },\n  \"IdentityServer\": {\n    \"Clients\": {\n      \"AuthSample\": {\n        \"Profile\": \"IdentityServerSPA\"\n      }\n    }\n  },\n\"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/README.md",
    "content": "# WorldCities\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"WorldCities\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"WorldCities:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": [],\n            \"assets\": [\n              \"src/assets\"\n            ]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\n              \"src/tsconfig.app.json\",\n              \"src/tsconfig.spec.json\"\n            ],\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"WorldCities-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"WorldCities:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"WorldCities\"\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/package.json",
    "content": "{\n  \"name\": \"worldcities\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run WorldCities:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/cdk\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/material\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"hammerjs\": \"2.0.8\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/api-authorization.constants.ts",
    "content": "export const ApplicationName = 'WorldCities';\n\nexport const ReturnUrlType = 'returnUrl';\n\nexport const QueryParameterNames = {\n  ReturnUrl: ReturnUrlType,\n  Message: 'message'\n};\n\nexport const LogoutActions = {\n  LogoutCallback: 'logout-callback',\n  Logout: 'logout',\n  LoggedOut: 'logged-out'\n};\n\nexport const LoginActions = {\n  Login: 'login',\n  LoginCallback: 'login-callback',\n  LoginFailed: 'login-failed',\n  Profile: 'profile',\n  Register: 'register'\n};\n\nlet applicationPaths: ApplicationPathsType = {\n  DefaultLoginRedirectPath: '/',\n  ApiAuthorizationClientConfigurationUrl: `/_configuration/${ApplicationName}`,\n  Login: `authentication/${LoginActions.Login}`,\n  LoginFailed: `authentication/${LoginActions.LoginFailed}`,\n  LoginCallback: `authentication/${LoginActions.LoginCallback}`,\n  Register: `authentication/${LoginActions.Register}`,\n  Profile: `authentication/${LoginActions.Profile}`,\n  LogOut: `authentication/${LogoutActions.Logout}`,\n  LoggedOut: `authentication/${LogoutActions.LoggedOut}`,\n  LogOutCallback: `authentication/${LogoutActions.LogoutCallback}`,\n  LoginPathComponents: [],\n  LoginFailedPathComponents: [],\n  LoginCallbackPathComponents: [],\n  RegisterPathComponents: [],\n  ProfilePathComponents: [],\n  LogOutPathComponents: [],\n  LoggedOutPathComponents: [],\n  LogOutCallbackPathComponents: [],\n  IdentityRegisterPath: '/Identity/Account/Register',\n  IdentityManagePath: '/Identity/Account/Manage'\n};\n\napplicationPaths = {\n  ...applicationPaths,\n  LoginPathComponents: applicationPaths.Login.split('/'),\n  LoginFailedPathComponents: applicationPaths.LoginFailed.split('/'),\n  RegisterPathComponents: applicationPaths.Register.split('/'),\n  ProfilePathComponents: applicationPaths.Profile.split('/'),\n  LogOutPathComponents: applicationPaths.LogOut.split('/'),\n  LoggedOutPathComponents: applicationPaths.LoggedOut.split('/'),\n  LogOutCallbackPathComponents: applicationPaths.LogOutCallback.split('/')\n};\n\ninterface ApplicationPathsType {\n  readonly DefaultLoginRedirectPath: string;\n  readonly ApiAuthorizationClientConfigurationUrl: string;\n  readonly Login: string;\n  readonly LoginFailed: string;\n  readonly LoginCallback: string;\n  readonly Register: string;\n  readonly Profile: string;\n  readonly LogOut: string;\n  readonly LoggedOut: string;\n  readonly LogOutCallback: string;\n  readonly LoginPathComponents: string [];\n  readonly LoginFailedPathComponents: string [];\n  readonly LoginCallbackPathComponents: string [];\n  readonly RegisterPathComponents: string [];\n  readonly ProfilePathComponents: string [];\n  readonly LogOutPathComponents: string [];\n  readonly LoggedOutPathComponents: string [];\n  readonly LogOutCallbackPathComponents: string [];\n  readonly IdentityRegisterPath: string;\n  readonly IdentityManagePath: string;\n}\n\nexport const ApplicationPaths: ApplicationPathsType = applicationPaths;\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/api-authorization.module.spec.ts",
    "content": "import { ApiAuthorizationModule } from './api-authorization.module';\n\ndescribe('ApiAuthorizationModule', () => {\n  let apiAuthorizationModule: ApiAuthorizationModule;\n\n  beforeEach(() => {\n    apiAuthorizationModule = new ApiAuthorizationModule();\n  });\n\n  it('should create an instance', () => {\n    expect(apiAuthorizationModule).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/api-authorization.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { LoginMenuComponent } from './login-menu/login-menu.component';\nimport { LoginComponent } from './login/login.component';\nimport { LogoutComponent } from './logout/logout.component';\nimport { RouterModule } from '@angular/router';\nimport { ApplicationPaths } from './api-authorization.constants';\nimport { HttpClientModule } from '@angular/common/http';\n\n@NgModule({\n  imports: [\n    CommonModule,\n    HttpClientModule,\n    RouterModule.forChild(\n      [\n        { path: ApplicationPaths.Register, component: LoginComponent },\n        { path: ApplicationPaths.Profile, component: LoginComponent },\n        { path: ApplicationPaths.Login, component: LoginComponent },\n        { path: ApplicationPaths.LoginFailed, component: LoginComponent },\n        { path: ApplicationPaths.LoginCallback, component: LoginComponent },\n        { path: ApplicationPaths.LogOut, component: LogoutComponent },\n        { path: ApplicationPaths.LoggedOut, component: LogoutComponent },\n        { path: ApplicationPaths.LogOutCallback, component: LogoutComponent }\n      ]\n    )\n  ],\n  declarations: [LoginMenuComponent, LoginComponent, LogoutComponent],\n  exports: [LoginMenuComponent, LoginComponent, LogoutComponent]\n})\nexport class ApiAuthorizationModule { }\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/authorize.guard.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeGuard } from './authorize.guard';\n\ndescribe('AuthorizeGuard', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeGuard]\n    });\n  });\n\n  it('should ...', inject([AuthorizeGuard], (guard: AuthorizeGuard) => {\n    expect(guard).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/authorize.guard.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { AuthorizeService } from './authorize.service';\nimport { tap } from 'rxjs/operators';\nimport { ApplicationPaths, QueryParameterNames } from './api-authorization.constants';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeGuard implements CanActivate {\n  constructor(private authorize: AuthorizeService, private router: Router) {\n  }\n  canActivate(\n    _next: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {\n      return this.authorize.isAuthenticated()\n        .pipe(tap(isAuthenticated => this.handleAuthorization(isAuthenticated, state)));\n  }\n\n  private handleAuthorization(isAuthenticated: boolean, state: RouterStateSnapshot) {\n    if (!isAuthenticated) {\n      this.router.navigate(ApplicationPaths.LoginPathComponents, {\n        queryParams: {\n          [QueryParameterNames.ReturnUrl]: state.url\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/authorize.interceptor.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeInterceptor } from './authorize.interceptor';\n\ndescribe('AuthorizeInterceptor', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeInterceptor]\n    });\n  });\n\n  it('should be created', inject([AuthorizeInterceptor], (service: AuthorizeInterceptor) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/authorize.interceptor.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';\nimport { Observable } from 'rxjs';\nimport { AuthorizeService } from './authorize.service';\nimport { mergeMap } from 'rxjs/operators';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeInterceptor implements HttpInterceptor {\n  constructor(private authorize: AuthorizeService) { }\n\n  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {\n    return this.authorize.getAccessToken()\n      .pipe(mergeMap(token => this.processRequestWithToken(token, req, next)));\n  }\n\n  // Checks if there is an access_token available in the authorize service\n  // and adds it to the request in case it's targeted at the same origin as the\n  // single page application.\n  private processRequestWithToken(token: string, req: HttpRequest<any>, next: HttpHandler) {\n    if (!!token && this.isSameOriginUrl(req)) {\n      req = req.clone({\n        setHeaders: {\n          Authorization: `Bearer ${token}`\n        }\n      });\n    }\n\n    return next.handle(req);\n  }\n\n  private isSameOriginUrl(req: any) {\n    // It's an absolute url with the same origin.\n    if (req.url.startsWith(`${window.location.origin}/`)) {\n      return true;\n    }\n\n    // It's a protocol relative url with the same origin.\n    // For example: //www.example.com/api/Products\n    if (req.url.startsWith(`//${window.location.host}/`)) {\n      return true;\n    }\n\n    // It's a relative url like /api/Products\n    if (/^\\/[^\\/].*/.test(req.url)) {\n      return true;\n    }\n\n    // It's an absolute or protocol relative url that\n    // doesn't have the same origin.\n    return false;\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/authorize.service.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeService } from './authorize.service';\n\ndescribe('AuthorizeService', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeService]\n    });\n  });\n\n  it('should be created', inject([AuthorizeService], (service: AuthorizeService) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/authorize.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { User, UserManager, WebStorageStateStore } from 'oidc-client';\nimport { BehaviorSubject, concat, from, Observable } from 'rxjs';\nimport { filter, map, mergeMap, take, tap } from 'rxjs/operators';\nimport { ApplicationPaths, ApplicationName } from './api-authorization.constants';\n\nexport type IAuthenticationResult =\n  SuccessAuthenticationResult |\n  FailureAuthenticationResult |\n  RedirectAuthenticationResult;\n\nexport interface SuccessAuthenticationResult {\n  status: AuthenticationResultStatus.Success;\n  state: any;\n}\n\nexport interface FailureAuthenticationResult {\n  status: AuthenticationResultStatus.Fail;\n  message: string;\n}\n\nexport interface RedirectAuthenticationResult {\n  status: AuthenticationResultStatus.Redirect;\n}\n\nexport enum AuthenticationResultStatus {\n  Success,\n  Redirect,\n  Fail\n}\n\nexport interface IUser {\n  name: string;\n}\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeService {\n  // By default pop ups are disabled because they don't work properly on Edge.\n  // If you want to enable pop up authentication simply set this flag to false.\n\n  private popUpDisabled = true;\n  private userManager: UserManager;\n  private userSubject: BehaviorSubject<IUser | null> = new BehaviorSubject(null);\n\n  public isAuthenticated(): Observable<boolean> {\n    return this.getUser().pipe(map(u => !!u));\n  }\n\n  public getUser(): Observable<IUser | null> {\n    return concat(\n      this.userSubject.pipe(take(1), filter(u => !!u)),\n      this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))),\n      this.userSubject.asObservable());\n  }\n\n  public getAccessToken(): Observable<string> {\n    return from(this.ensureUserManagerInitialized())\n      .pipe(mergeMap(() => from(this.userManager.getUser())),\n        map(user => user && user.access_token));\n  }\n\n  // We try to authenticate the user in three different ways:\n  // 1) We try to see if we can authenticate the user silently. This happens\n  //    when the user is already logged in on the IdP and is done using a hidden iframe\n  //    on the client.\n  // 2) We try to authenticate the user using a PopUp Window. This might fail if there is a\n  //    Pop-Up blocker or the user has disabled PopUps.\n  // 3) If the two methods above fail, we redirect the browser to the IdP to perform a traditional\n  //    redirect flow.\n  public async signIn(state: any): Promise<IAuthenticationResult> {\n    await this.ensureUserManagerInitialized();\n    let user: User = null;\n    try {\n      user = await this.userManager.signinSilent(this.createArguments());\n      this.userSubject.next(user.profile);\n      return this.success(state);\n    } catch (silentError) {\n      // User might not be authenticated, fallback to popup authentication\n      console.log('Silent authentication error: ', silentError);\n\n      try {\n        if (this.popUpDisabled) {\n          throw new Error('Popup disabled. Change \\'authorize.service.ts:AuthorizeService.popupDisabled\\' to false to enable it.');\n        }\n        user = await this.userManager.signinPopup(this.createArguments());\n        this.userSubject.next(user.profile);\n        return this.success(state);\n      } catch (popupError) {\n        if (popupError.message === 'Popup window closed') {\n          // The user explicitly cancelled the login action by closing an opened popup.\n          return this.error('The user closed the window.');\n        } else if (!this.popUpDisabled) {\n          console.log('Popup authentication error: ', popupError);\n        }\n\n        // PopUps might be blocked by the user, fallback to redirect\n        try {\n          await this.userManager.signinRedirect(this.createArguments(state));\n          return this.redirect();\n        } catch (redirectError) {\n          console.log('Redirect authentication error: ', redirectError);\n          return this.error(redirectError);\n        }\n      }\n    }\n  }\n\n  public async completeSignIn(url: string): Promise<IAuthenticationResult> {\n    try {\n      await this.ensureUserManagerInitialized();\n      const user = await this.userManager.signinCallback(url);\n      this.userSubject.next(user && user.profile);\n      return this.success(user && user.state);\n    } catch (error) {\n      console.log('There was an error signing in: ', error);\n      return this.error('There was an error signing in.');\n    }\n  }\n\n  public async signOut(state: any): Promise<IAuthenticationResult> {\n    try {\n      if (this.popUpDisabled) {\n        throw new Error('Popup disabled. Change \\'authorize.service.ts:AuthorizeService.popupDisabled\\' to false to enable it.');\n      }\n\n      await this.ensureUserManagerInitialized();\n      await this.userManager.signoutPopup(this.createArguments());\n      this.userSubject.next(null);\n      return this.success(state);\n    } catch (popupSignOutError) {\n      console.log('Popup signout error: ', popupSignOutError);\n      try {\n        await this.userManager.signoutRedirect(this.createArguments(state));\n        return this.redirect();\n      } catch (redirectSignOutError) {\n        console.log('Redirect signout error: ', popupSignOutError);\n        return this.error(redirectSignOutError);\n      }\n    }\n  }\n\n  public async completeSignOut(url: string): Promise<IAuthenticationResult> {\n    await this.ensureUserManagerInitialized();\n    try {\n      const state = await this.userManager.signoutCallback(url);\n      this.userSubject.next(null);\n      return this.success(state && state.data);\n    } catch (error) {\n      console.log(`There was an error trying to log out '${error}'.`);\n      return this.error(error);\n    }\n  }\n\n  private createArguments(state?: any): any {\n    return { useReplaceToNavigate: true, data: state };\n  }\n\n  private error(message: string): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Fail, message };\n  }\n\n  private success(state: any): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Success, state };\n  }\n\n  private redirect(): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Redirect };\n  }\n\n  private async ensureUserManagerInitialized(): Promise<void> {\n    if (this.userManager !== undefined) {\n      return;\n    }\n\n    const response = await fetch(ApplicationPaths.ApiAuthorizationClientConfigurationUrl);\n    if (!response.ok) {\n      throw new Error(`Could not load settings for '${ApplicationName}'`);\n    }\n\n    const settings: any = await response.json();\n    settings.automaticSilentRenew = true;\n    settings.includeIdTokenInSilentRenew = true;\n    this.userManager = new UserManager(settings);\n\n    this.userManager.events.addUserSignedOut(async () => {\n      await this.userManager.removeUser();\n      this.userSubject.next(null);\n    });\n  }\n\n  private getUserFromStorage(): Observable<IUser> {\n    return from(this.ensureUserManagerInitialized())\n      .pipe(\n        mergeMap(() => this.userManager.getUser()),\n        map(u => u && u.profile));\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/login/login.component.css",
    "content": ""
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/login/login.component.html",
    "content": "<p>{{ message | async }}</p>"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/login/login.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LoginComponent } from './login.component';\n\ndescribe('LoginComponent', () => {\n  let component: LoginComponent;\n  let fixture: ComponentFixture<LoginComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LoginComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LoginComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/login/login.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthorizeService, AuthenticationResultStatus } from '../authorize.service';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { BehaviorSubject } from 'rxjs';\nimport { LoginActions, QueryParameterNames, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants';\n\n// The main responsibility of this component is to handle the user's login process.\n// This is the starting point for the login process. Any component that needs to authenticate\n// a user can simply perform a redirect to this component with a returnUrl query parameter and\n// let the component perform the login and return back to the return url.\n@Component({\n  selector: 'app-login',\n  templateUrl: './login.component.html',\n  styleUrls: ['./login.component.css']\n})\nexport class LoginComponent implements OnInit {\n  public message = new BehaviorSubject<string>(null);\n\n  constructor(\n    private authorizeService: AuthorizeService,\n    private activatedRoute: ActivatedRoute,\n    private router: Router) { }\n\n  async ngOnInit() {\n    const action = this.activatedRoute.snapshot.url[1];\n    switch (action.path) {\n      case LoginActions.Login:\n        await this.login(this.getReturnUrl());\n        break;\n      case LoginActions.LoginCallback:\n        await this.processLoginCallback();\n        break;\n      case LoginActions.LoginFailed:\n        const message = this.activatedRoute.snapshot.queryParamMap.get(QueryParameterNames.Message);\n        this.message.next(message);\n        break;\n      case LoginActions.Profile:\n        this.redirectToProfile();\n        break;\n      case LoginActions.Register:\n        this.redirectToRegister();\n        break;\n      default:\n        throw new Error(`Invalid action '${action}'`);\n    }\n  }\n\n\n  private async login(returnUrl: string): Promise<void> {\n    const state: INavigationState = { returnUrl };\n    const result = await this.authorizeService.signIn(state);\n    this.message.next(undefined);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        break;\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(returnUrl);\n        break;\n      case AuthenticationResultStatus.Fail:\n        await this.router.navigate(ApplicationPaths.LoginFailedPathComponents, {\n          queryParams: { [QueryParameterNames.Message]: result.message }\n        });\n        break;\n      default:\n        throw new Error(`Invalid status result ${(result as any).status}.`);\n    }\n  }\n\n  private async processLoginCallback(): Promise<void> {\n    const url = window.location.href;\n    const result = await this.authorizeService.completeSignIn(url);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        // There should not be any redirects as completeSignIn never redirects.\n        throw new Error('Should not redirect.');\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(this.getReturnUrl(result.state));\n        break;\n      case AuthenticationResultStatus.Fail:\n        this.message.next(result.message);\n        break;\n    }\n  }\n\n  private redirectToRegister(): any {\n    this.redirectToApiAuthorizationPath(\n      `${ApplicationPaths.IdentityRegisterPath}?returnUrl=${encodeURI('/' + ApplicationPaths.Login)}`);\n  }\n\n  private redirectToProfile(): void {\n    this.redirectToApiAuthorizationPath(ApplicationPaths.IdentityManagePath);\n  }\n\n  private async navigateToReturnUrl(returnUrl: string) {\n    // It's important that we do a replace here so that we remove the callback uri with the\n    // fragment containing the tokens from the browser history.\n    await this.router.navigateByUrl(returnUrl, {\n      replaceUrl: true\n    });\n  }\n\n  private getReturnUrl(state?: INavigationState): string {\n    const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl;\n    // If the url is comming from the query string, check that is either\n    // a relative url or an absolute url\n    if (fromQuery &&\n      !(fromQuery.startsWith(`${window.location.origin}/`) ||\n        /\\/[^\\/].*/.test(fromQuery))) {\n      // This is an extra check to prevent open redirects.\n      throw new Error('Invalid return url. The return url needs to have the same origin as the current page.');\n    }\n    return (state && state.returnUrl) ||\n      fromQuery ||\n      ApplicationPaths.DefaultLoginRedirectPath;\n  }\n\n  private redirectToApiAuthorizationPath(apiAuthorizationPath: string) {\n    // It's important that we do a replace here so that when the user hits the back arrow on the\n    // browser they get sent back to where it was on the app instead of to an endpoint on this\n    // component.\n    const redirectUrl = `${window.location.origin}${apiAuthorizationPath}`;\n    window.location.replace(redirectUrl);\n  }\n}\n\ninterface INavigationState {\n  [ReturnUrlType]: string;\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.css",
    "content": ""
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.html",
    "content": "<ul class=\"navbar-nav\" *ngIf=\"isAuthenticated | async\">\n    <li class=\"nav-item\">\n        <a  class=\"nav-link text-dark\" [routerLink]='[\"/authentication/profile\"]' title=\"Manage\">Hello {{ userName | async }}</a>\n    </li>\n    <li class=\"nav-item\">\n        <a  class=\"nav-link text-dark\" [routerLink]='[\"/authentication/logout\"]' [state]='{ local: true }' title=\"Logout\">Logout</a>\n    </li>\n</ul>\n<ul class=\"navbar-nav\" *ngIf=\"!(isAuthenticated | async)\">\n  <li class=\"nav-item\">\n        <a class=\"nav-link text-dark\" [routerLink]='[\"/authentication/register\"]'>Register</a>\n    </li>\n    <li class=\"nav-item\">\n        <a class=\"nav-link text-dark\" [routerLink]='[\"/authentication/login\"]'>Login</a>\n    </li>\n</ul>\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LoginMenuComponent } from './login-menu.component';\n\ndescribe('LoginMenuComponent', () => {\n  let component: LoginMenuComponent;\n  let fixture: ComponentFixture<LoginMenuComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LoginMenuComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LoginMenuComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthorizeService } from '../authorize.service';\nimport { Observable } from 'rxjs';\nimport { map, tap } from 'rxjs/operators';\n\n@Component({\n  selector: 'app-login-menu',\n  templateUrl: './login-menu.component.html',\n  styleUrls: ['./login-menu.component.css']\n})\nexport class LoginMenuComponent implements OnInit {\n  public isAuthenticated: Observable<boolean>;\n  public userName: Observable<string>;\n\n  constructor(private authorizeService: AuthorizeService) { }\n\n  ngOnInit() {\n    this.isAuthenticated = this.authorizeService.isAuthenticated();\n    this.userName = this.authorizeService.getUser().pipe(map(u => u && u.name));\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/logout/logout.component.css",
    "content": ""
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/logout/logout.component.html",
    "content": "<p>{{ message | async }}</p>"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/logout/logout.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LogoutComponent } from './logout.component';\n\ndescribe('LogoutComponent', () => {\n  let component: LogoutComponent;\n  let fixture: ComponentFixture<LogoutComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LogoutComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LogoutComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/api-authorization/logout/logout.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthenticationResultStatus, AuthorizeService } from '../authorize.service';\nimport { BehaviorSubject } from 'rxjs';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { take } from 'rxjs/operators';\nimport { LogoutActions, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants';\n\n// The main responsibility of this component is to handle the user's logout process.\n// This is the starting point for the logout process, which is usually initiated when a\n// user clicks on the logout button on the LoginMenu component.\n@Component({\n  selector: 'app-logout',\n  templateUrl: './logout.component.html',\n  styleUrls: ['./logout.component.css']\n})\nexport class LogoutComponent implements OnInit {\n  public message = new BehaviorSubject<string>(null);\n\n  constructor(\n    private authorizeService: AuthorizeService,\n    private activatedRoute: ActivatedRoute,\n    private router: Router) { }\n\n  async ngOnInit() {\n    const action = this.activatedRoute.snapshot.url[1];\n    switch (action.path) {\n      case LogoutActions.Logout:\n        if (!!window.history.state.local) {\n          await this.logout(this.getReturnUrl());\n        } else {\n          // This prevents regular links to <app>/authentication/logout from triggering a logout\n          this.message.next('The logout was not initiated from within the page.');\n        }\n\n        break;\n      case LogoutActions.LogoutCallback:\n        await this.processLogoutCallback();\n        break;\n      case LogoutActions.LoggedOut:\n        this.message.next('You successfully logged out!');\n        break;\n      default:\n        throw new Error(`Invalid action '${action}'`);\n    }\n  }\n\n  private async logout(returnUrl: string): Promise<void> {\n    const state: INavigationState = { returnUrl };\n    const isauthenticated = await this.authorizeService.isAuthenticated().pipe(\n      take(1)\n    ).toPromise();\n    if (isauthenticated) {\n      const result = await this.authorizeService.signOut(state);\n      switch (result.status) {\n        case AuthenticationResultStatus.Redirect:\n          break;\n        case AuthenticationResultStatus.Success:\n          await this.navigateToReturnUrl(returnUrl);\n          break;\n        case AuthenticationResultStatus.Fail:\n          this.message.next(result.message);\n          break;\n        default:\n          throw new Error('Invalid authentication result status.');\n      }\n    } else {\n      this.message.next('You successfully logged out!');\n    }\n  }\n\n  private async processLogoutCallback(): Promise<void> {\n    const url = window.location.href;\n    const result = await this.authorizeService.completeSignOut(url);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        // There should not be any redirects as the only time completeAuthentication finishes\n        // is when we are doing a redirect sign in flow.\n        throw new Error('Should not redirect.');\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(this.getReturnUrl(result.state));\n        break;\n      case AuthenticationResultStatus.Fail:\n        this.message.next(result.message);\n        break;\n      default:\n        throw new Error('Invalid authentication result status.');\n    }\n  }\n\n  private async navigateToReturnUrl(returnUrl: string) {\n    await this.router.navigateByUrl(returnUrl, {\n      replaceUrl: true\n    });\n  }\n\n  private getReturnUrl(state?: INavigationState): string {\n    const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl;\n    // If the url is comming from the query string, check that is either\n    // a relative url or an absolute url\n    if (fromQuery &&\n      !(fromQuery.startsWith(`${window.location.origin}/`) ||\n        /\\/[^\\/].*/.test(fromQuery))) {\n      // This is an extra check to prevent open redirects.\n      throw new Error('Invalid return url. The return url needs to have the same origin as the current page.');\n    }\n    return (state && state.returnUrl) ||\n      fromQuery ||\n      ApplicationPaths.LoggedOut;\n  }\n}\n\ninterface INavigationState {\n  [ReturnUrlType]: string;\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/angular-material.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatPaginatorModule } from '@angular/material/paginator';\nimport { MatSortModule } from '@angular/material/sort';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\n\n@NgModule({\n  imports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ],\n  exports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ]\n})\n\nexport class AngularMaterialModule { }\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/app.component.html",
    "content": "<body>\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { BaseFormComponent } from './base.form.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { CitiesComponent } from './cities/cities.component';\nimport { CityEditComponent } from './cities/city-edit.component';\nimport { CountriesComponent } from './countries/countries.component';\nimport { CountryEditComponent } from './countries/country-edit.component';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { AngularMaterialModule } from './angular-material.module';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\n\nimport { ApiAuthorizationModule } from 'src/api-authorization/api-authorization.module';\nimport { AuthorizeGuard } from 'src/api-authorization/authorize.guard';\nimport { AuthorizeInterceptor } from 'src/api-authorization/authorize.interceptor';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    BaseFormComponent,\n    NavMenuComponent,\n    HomeComponent,\n    CitiesComponent,\n    CityEditComponent,\n    CountriesComponent,\n    CountryEditComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    ApiAuthorizationModule,\n    RouterModule.forRoot([\n      {\n        path: '',\n        component: HomeComponent,\n        pathMatch: 'full'\n      },\n      {\n        path: 'cities',\n        component: CitiesComponent\n      },\n      {\n        path: 'city/:id',\n        component: CityEditComponent,\n        canActivate: [AuthorizeGuard]\n      },\n      {\n        path: 'city',\n        component: CityEditComponent,\n        canActivate: [AuthorizeGuard]\n      },\n      {\n        path: 'countries',\n        component: CountriesComponent\n      },\n      {\n        path: 'country/:id',\n        component: CountryEditComponent,\n        canActivate: [AuthorizeGuard]\n      },\n      {\n        path: 'country',\n        component: CountryEditComponent,\n        canActivate: [AuthorizeGuard]\n      }\n    ]),\n    BrowserAnimationsModule,\n    AngularMaterialModule,\n    ReactiveFormsModule\n    ],\n  providers: [\n    {\n      provide: HTTP_INTERCEPTORS,\n      useClass: AuthorizeInterceptor,\n      multi: true\n    }\n  ],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/base.form.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { FormGroup } from '@angular/forms';\n\n@Component({\n    template: ''\n})  \nexport class BaseFormComponent {\n\n    // the form model\n    form: FormGroup;\n\n    constructor() {\n    }\n\n    // retrieve a FormControl\n    getControl(name: string) {\n        return this.form.get(name);\n    }\n\n    // returns TRUE if the FormControl is valid\n    isValid(name: string) {\n        var e = this.getControl(name);\n        return e && e.valid;\n    }\n\n    // returns TRUE if the FormControl has been changed\n    isChanged(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched);\n    }\n\n    // returns TRUE if the FormControl is raising an error,\n    // i.e. an invalid state after user changes\n    hasError(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched) && e.invalid;\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/base.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Observable } from 'rxjs';\n\n@Injectable()\nexport abstract class BaseService {\n    constructor(\n        public http: HttpClient,\n        public baseUrl: string\n    ) {\n    }\n\n    abstract getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string): Observable<ApiResult>;\n\n    abstract get<T>(id: number): Observable<T>;\n    abstract put<T>(item: T): Observable<T>;\n    abstract post<T>(item: T): Observable<T>;\n}\n\nexport interface ApiResult<T> {\n    data: T[];\n    pageIndex: number;\n    pageSize: number;\n    totalCount: number;\n    totalPages: number;\n    sortColumn: string;\n    sortOrder: string;\n    filterColumn: string;\n    filterQuery: string;\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/cities/cities.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/cities/cities.component.html",
    "content": "<h1>Cities</h1>\n\n<p>Here's a list of cities: feel free to play with it.</p>\n\n<p *ngIf=\"!cities\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"cities\">\n  <button type=\"button\"\n          [routerLink]=\"['/city']\"\n          class=\"btn btn-success\">\n      Add a new City\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!cities\">\n    <input matInput (keyup)=\"loadData($event.target.value)\"\n        placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"cities\" class=\"mat-elevation-z8\" [hidden]=\"!cities\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/city', city.id]\">{{city.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"lat\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Latitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lat}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"lon\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Longitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lon}} </td>\n  </ng-container>\n\n  <!-- CountryName Column -->\n  <ng-container matColumnDef=\"countryName\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Country</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/country', city.countryId]\">{{city.countryName}}</a>\n    </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!cities\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/cities/cities.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { RouterTestingModule } from '@angular/router/testing';\nimport { AngularMaterialModule } from '../angular-material.module';\nimport { of } from 'rxjs';\n\nimport { CitiesComponent } from './cities.component';\nimport { City } from './city';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\ndescribe('CitiesComponent', () => {\n  let fixture: ComponentFixture<CitiesComponent>;\n  let component: CitiesComponent;\n\n  // async beforeEach(): TestBed initialization\n  beforeEach(async(() => {\n\n    // Create a mock cityService object with a mock 'getData' method\n    let cityService = jasmine.createSpyObj<CityService>(\n      'CityService', ['getData']\n    );\n\n    // Configure the 'getData' spy method\n    cityService.getData.and.returnValue(\n      // return an Observable with some test data\n      of<ApiResult<City>>(<ApiResult<City>>{\n        data: [\n          <City>{\n            name: 'TestCity1',\n            id: 1, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          },\n          <City>{\n            name: 'TestCity2',\n            id: 2, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          },\n          <City>{\n            name: 'TestCity3',\n            id: 3, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          }\n        ],\n        totalCount: 3,\n        pageIndex: 0,\n        pageSize: 10\n      }));\n\n    TestBed.configureTestingModule({\n      declarations: [CitiesComponent],\n      imports: [\n        BrowserAnimationsModule,\n        AngularMaterialModule,\n        RouterTestingModule\n      ],\n      providers: [\n        {\n          provide: CityService,\n          useValue: cityService\n        }\n      ]\n    })\n      .compileComponents();\n  }));\n\n  // synchronous beforeEach(): fixtures and components setup\n  beforeEach(() => {\n    fixture = TestBed.createComponent(CitiesComponent);\n    component = fixture.componentInstance;\n\n    component.paginator = jasmine.createSpyObj(\n      \"MatPaginator\", [\"length\", \"pageIndex\", \"pageSize\"]\n    );\n\n    fixture.detectChanges();\n  });\n\n  it('should display a \"Cities\" title', async(() => {\n    let title = fixture.nativeElement\n      .querySelector('h1');\n    expect(title.textContent).toEqual('Cities');\n  }));\n\n  it('should contain a table with a list of one or more cities', async(() => {\n    let table = fixture.nativeElement\n      .querySelector('table.mat-table');\n    let tableRows = table\n      .querySelectorAll('tr.mat-row');\n    expect(tableRows.length).toBeGreaterThan(0);\n  }));\n});\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/cities/cities.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { City } from './city';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-cities',\n  templateUrl: './cities.component.html',\n  styleUrls: ['./cities.component.css']\n})\nexport class CitiesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'lat', 'lon', 'countryName'];\n  public cities: MatTableDataSource<City>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private cityService: CityService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n    var sortColumn = (this.sort)\n      ? this.sort.active\n      : this.defaultSortColumn;\n\n    var sortOrder = (this.sort)\n      ? this.sort.direction\n      : this.defaultSortOrder;\n\n    var filterColumn = (this.filterQuery)\n      ? this.defaultFilterColumn\n      : null;\n\n    var filterQuery = (this.filterQuery)\n      ? this.filterQuery\n      : null;\n\n    this.cityService.getData<ApiResult<City>>(\n      event.pageIndex,\n      event.pageSize,\n      sortColumn,\n      sortOrder,\n      filterColumn,\n      filterQuery)\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.cities = new MatTableDataSource<City>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/cities/city-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/cities/city-edit.component.html",
    "content": "<div class=\"city-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !city\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n\n        <div *ngIf=\"form.invalid && form.errors?.isDupeCity\"\n             class=\"alert alert-danger\">\n              <strong>ERROR</strong>:\n              A city with the same <i>name</i>, <i>lat</i>,\n              <i>lon</i> and <i>country</i> already exists.\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"name\">City name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"City name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"hasError('name')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"lat\">City latitude:</label>\n            <br />\n            <input type=\"text\" id=\"lat\"\n                   formControlName=\"lat\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lat')\"\n                  class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lat').errors?.required\">\n                  Latitude is required.\n                </div>\n                <div *ngIf=\"form.get('lat').errors?.pattern\">\n                  Latitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n\n\n        <div class=\"form-group\">\n            <label for=\"lon\">City longitude:</label>\n            <br />\n            <input type=\"text\" id=\"lon\"\n                   formControlName=\"lon\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lon')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lon').errors?.required\">\n                  Longitude is required.\n                </div>\n                <div *ngIf=\"form.get('lon').errors?.pattern\">\n                  Longitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"countryId\">Country:</label>\n            <br />\n            <mat-form-field *ngIf=\"countries\">\n              <mat-label>Select a Country...</mat-label>\n              <mat-select id=\"countryId\" formControlName=\"countryId\">\n                <mat-option *ngFor=\"let country of countries\" [value]=\"country.id\">\n                  {{country.name}}\n                </mat-option>\n              </mat-select>\n            </mat-form-field>\n\n            <div *ngIf=\"hasError('countryId')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('countryId').errors?.required\">\n                  Please select a Country.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/cities']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n\n<!-- Form debug info panel -->\n<div class=\"card bg-light mb-3\">\n  <div class=\"card-header\">Form Debug Info</div>\n  <div class=\"card-body\">\n    <div class=\"card-text\">\n      <div><strong>Form value:</strong></div>\n      <div class=\"help-block\">\n          {{ form.value | json }}\n      </div>\n      <div class=\"mt-2\"><strong>Form status:</strong></div>\n      <div class=\"help-block\">\n          {{ form.status | json }}\n      </div>\n    </div>\n  </div>\n</div>\n\n<!-- Form activity log panel -->\n<div class=\"card bg-light mb-3\">\n  <div class=\"card-header\">Form Activity Log</div>\n  <div class=\"card-body\">\n    <div class=\"card-text\">\n      <div class=\"help-block\">\n        <span *ngIf=\"activityLog\" \n            [innerHTML]=\"activityLog\"></span>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/cities/city-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormControl, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { City } from './city';\nimport { Country } from '../countries/country';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-city-edit',\n  templateUrl: './city-edit.component.html',\n  styleUrls: ['./city-edit.component.css']\n})\nexport class CityEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  city: City;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new city,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  // the countries array for the select\n  countries: Country[];\n\n  // Activity Log (for debugging purposes)\n  activityLog: string = '';\n\n  constructor(\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private cityService: CityService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = new FormGroup({\n      name: new FormControl('', Validators.required),\n      lat: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      lon: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      countryId: new FormControl('', Validators.required)\n    }, null, this.isDupeCity());\n\n    // react to form changes\n    this.form.valueChanges\n      .subscribe(val => {\n        if (!this.form.dirty) {\n          this.log(\"Form Model has been loaded.\");\n        }\n        else {\n          this.log(\"Form was updated by the user.\");\n        }\n      });\n\n    // react to changes in the form.name control\n    this.form.get(\"name\")!.valueChanges\n      .subscribe(val => {\n        if (!this.form.dirty) {\n          this.log(\"Name has been loaded with initial values.\");\n        }\n        else {\n          this.log(\"Name was updated by the user.\");\n        }\n      });\n\n    this.loadData();\n  }\n\n  log(str: string) {\n    this.activityLog += \"[\"\n      + new Date().toLocaleString()\n      + \"] \" + str + \"<br />\";\n  }\n\n  loadData() {\n\n    // load countries\n    this.loadCountries();\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the city from the server\n      this.cityService.get<City>(this.id).subscribe(result => {\n        this.city = result;\n        this.title = \"Edit - \" + this.city.name;\n\n        // update the form with the city value\n        this.form.patchValue(this.city);\n      }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new City\";\n    }\n  }\n\n  loadCountries() {\n    // fetch all the countries from the server\n    this.cityService.getCountries<ApiResult<Country>>(\n      0,\n      9999,\n      \"name\",\n      null,\n      null,\n      null,\n    ).subscribe(result => {\n      this.countries = result.data;\n    }, error => console.error(error));\n  }\n\n  onSubmit() {\n\n    var city = (this.id) ? this.city : <City>{};\n\n    city.name = this.form.get(\"name\").value;\n    city.lat = +this.form.get(\"lat\").value;\n    city.lon = +this.form.get(\"lon\").value;\n    city.countryId = +this.form.get(\"countryId\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.cityService\n        .put<City>(city)\n        .subscribe(result => {\n\n          console.log(\"City \" + city.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.cityService\n        .post<City>(city)\n        .subscribe(result => {\n\n          console.log(\"City \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeCity(): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n      var city = <City>{};\n      city.id = (this.id) ? this.id : 0;\n      city.name = this.form.get(\"name\").value;\n      city.lat = +this.form.get(\"lat\").value;\n      city.lon = +this.form.get(\"lon\").value;\n      city.countryId = +this.form.get(\"countryId\").value;\n\n      return this.cityService.isDupeCity(city)\n        .pipe(map(result => {\n          return (result ? { isDupeCity: true } : null);\n        }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/cities/city.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CityService\n    extends BaseService {\n    constructor(\n        http: HttpClient,\n        @Inject('BASE_URL') baseUrl: string) {\n        super(http, baseUrl);\n    }\n\n    getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Cities';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    get<City>(id): Observable<City> {\n        var url = this.baseUrl + \"api/Cities/\" + id;\n        return this.http.get<City>(url);\n    }\n\n    put<City>(item): Observable<City> {\n        var url = this.baseUrl + \"api/Cities/\" + item.id;\n        return this.http.put<City>(url, item);\n    }\n\n    post<City>(item): Observable<City> {\n        var url = this.baseUrl + \"api/Cities\";\n        return this.http.post<City>(url, item);\n    }\n\n    getCountries<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Countries';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    isDupeCity(item): Observable<boolean> {\n        var url = this.baseUrl + \"api/Cities/IsDupeCity\";\n        return this.http.post<boolean>(url, item);\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/cities/city.ts",
    "content": "export interface City {\n    id: number;\n    name: string;\n    lat: number;\n    lon: number;\n    countryId: number;\n    countryName: string;\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/countries/countries.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/countries/countries.component.html",
    "content": "<h1>Countries</h1>\n\n<p>Here's a list of countries: feel free to play with it.</p>\n\n<p *ngIf=\"!countries\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"countries\">\n  <button type=\"button\"\n          [routerLink]=\"['/country']\"\n          class=\"btn btn-success\">\n      Add a new Country\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!countries\">\n  <input matInput (keyup)=\"loadData($event.target.value)\"\n      placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"countries\" class=\"mat-elevation-z8\" [hidden]=\"!countries\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let country\">\n      <a [routerLink]=\"['/country', country.id]\">{{country.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"iso2\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 2</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso2}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"iso3\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 3</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso3}} </td>\n  </ng-container>\n\n  <!-- TotCities Column -->\n  <ng-container matColumnDef=\"totCities\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Tot. Cities</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.totCities}} </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!countries\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/countries/countries.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { Country } from './country';\nimport { CountryService } from './country.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-countries',\n  templateUrl: './countries.component.html',\n  styleUrls: ['./countries.component.css']\n})\nexport class CountriesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'iso2', 'iso3', 'totCities'];\n  public countries: MatTableDataSource<Country>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private countryService: CountryService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n    var sortColumn = (this.sort)\n      ? this.sort.active\n      : this.defaultSortColumn;\n\n    var sortOrder = (this.sort)\n      ? this.sort.direction\n      : this.defaultSortOrder;\n\n    var filterColumn = (this.filterQuery)\n      ? this.defaultFilterColumn\n      : null;\n\n    var filterQuery = (this.filterQuery)\n      ? this.filterQuery\n      : null;\n\n    this.countryService.getData<ApiResult<Country>>(\n      event.pageIndex,\n      event.pageSize,\n      sortColumn,\n      sortOrder,\n      filterColumn,\n      filterQuery)\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.countries = new MatTableDataSource<Country>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/countries/country-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/countries/country-edit.component.html",
    "content": "<div class=\"country-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !country\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n     \n        <div class=\"form-group\">\n            <label for=\"name\">Country name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"Country name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"form.get('name').invalid &&\n                 (form.get('name').dirty || form.get('name').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n                <div *ngIf=\"form.get('name').errors?.isDupeField\">\n                  Name does already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"iso2\">ISO 3166-1 ALPHA-2 Country Code (2 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso2\"\n                   formControlName=\"iso2\" required\n                   placeholder=\"2 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso2').invalid &&\n                 (form.get('iso2').dirty || form.get('iso2').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso2').errors?.required\">\n                  ISO 3166-1 ALPHA-2 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.pattern\">\n                  ISO 3166-1 ALPHA-2 country code requires 2 letters.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-2 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n              <div class=\"form-group\">\n            <label for=\"iso3\">ISO 3166-1 ALPHA-3 Country Code (3 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso3\"\n                   formControlName=\"iso3\" required\n                   placeholder=\"3 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso3').invalid &&\n                 (form.get('iso3').dirty || form.get('iso3').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso3').errors?.required\">\n                  ISO 3166-1 ALPHA-3 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.pattern\">\n                  ISO 3166-1 ALPHA-3 country code requires 3 letters.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-3 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/countries']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/countries/country-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormBuilder, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { map } from 'rxjs/operators';\nimport { Observable } from 'rxjs';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { Country } from '../countries/country';\nimport { CountryService } from './country.service';\n\n@Component({\n  selector: 'app-country-edit',\n  templateUrl: './country-edit.component.html',\n  styleUrls: ['./country-edit.component.css']\n})\nexport class CountryEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  country: Country;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new country,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  constructor(\n    private fb: FormBuilder,\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private countryService: CountryService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = this.fb.group({\n      name: ['',\n        Validators.required,\n        this.isDupeField(\"name\")\n      ],\n      iso2: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{2}/)\n        ],\n        this.isDupeField(\"iso2\")\n      ],\n      iso3: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{3}/)\n        ],\n        this.isDupeField(\"iso3\")\n      ]\n    });\n\n    this.loadData();\n  }\n\n  loadData() {\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the country from the server\n      this.countryService.get<Country>(this.id)\n        .subscribe(result => {\n          this.country = result;\n          this.title = \"Edit - \" + this.country.name;\n\n          // update the form with the country value\n          this.form.patchValue(this.country);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new Country\";\n    }\n  }\n\n  onSubmit() {\n\n    var country = (this.id) ? this.country : <Country>{};\n\n    country.name = this.form.get(\"name\").value;\n    country.iso2 = this.form.get(\"iso2\").value;\n    country.iso3 = this.form.get(\"iso3\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.countryService\n        .put<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + country.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.countryService\n        .post<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeField(fieldName: string): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n\n      var countryId = (this.id) ? this.id.toString() : \"0\";\n\n      return this.countryService.isDupeField(\n        countryId,\n        fieldName,\n        control.value)\n        .pipe(map(result => {\n          return (result ? { isDupeField: true } : null);\n        }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/countries/country.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CountryService\n    extends BaseService {\n    constructor(\n        http: HttpClient,\n        @Inject('BASE_URL') baseUrl: string) {\n        super(http, baseUrl);\n    }\n\n    getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Countries';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    get<Country>(id): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries/\" + id;\n        return this.http.get<Country>(url);\n    }\n\n    put<Country>(item): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries/\" + item.id;\n        return this.http.put<Country>(url, item);\n    }\n\n    post<Country>(item): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries\";\n        return this.http.post<Country>(url, item);\n    }\n\n    isDupeField(countryId, fieldName, fieldValue): Observable<boolean> {\n        var params = new HttpParams()\n            .set(\"countryId\", countryId)\n            .set(\"fieldName\", fieldName)\n            .set(\"fieldValue\", fieldValue);\n        var url = this.baseUrl + \"api/Countries/IsDupeField\";\n        return this.http.post<boolean>(url, null, { params });\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/countries/country.ts",
    "content": "export interface Country {\n    id: number;\n    name: string;\n    iso2: string;\n    iso3: string;\n    totCities: number;\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">WorldCities</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <app-login-menu></app-login-menu>\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/cities']\"\n              >Cities</a\n            >\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/countries']\"\n              >Countries</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>WorldCities</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n      <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n</head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/main.ts",
    "content": "import 'hammerjs';\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_10/WorldCities/Controllers/CitiesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CitiesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CitiesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Cities/?pageIndex=0&pageSize=10\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CityDTO>>> GetCities(\n                int pageIndex = 0,\n                int pageSize = 10,\n                string sortColumn = null,\n                string sortOrder = null,\n                string filterColumn = null,\n                string filterQuery = null)\n        {\n            return await ApiResult<CityDTO>.CreateAsync(\n                    _context.Cities\n                        .Select(c => new CityDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            Lat = c.Lat,\n                            Lon = c.Lon,\n                            CountryId = c.Country.Id,\n                            CountryName = c.Country.Name\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        // GET: api/Cities/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<City>> GetCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            return city;\n        }\n\n        // PUT: api/Cities/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCity(int id, City city)\n        {\n            if (id != city.Id)\n            {\n                return BadRequest();\n            }\n\n            //var sourceCity = _context.Cities.Where(i => i.Id == city.Id).FirstOrDefault();\n            //if (sourceCity == null) return BadRequest();\n            //sourceCity.Name = city.Name;\n            //sourceCity.Lat = city.Lat;\n            //sourceCity.Lon = city.Lon;\n\n            _context.Entry(city).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CityExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Cities\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPost]\n        public async Task<ActionResult<City>> PostCity(City city)\n        {\n            _context.Cities.Add(city);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCity\", new { id = city.Id }, city);\n        }\n\n        // DELETE: api/Cities/5\n        [Authorize]\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<City>> DeleteCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            _context.Cities.Remove(city);\n            await _context.SaveChangesAsync();\n\n            return city;\n        }\n\n        private bool CityExists(int id)\n        {\n            return _context.Cities.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeCity\")]\n        public bool IsDupeCity(City city)\n        {\n            return _context.Cities.Any(\n                e => e.Name == city.Name\n                && e.Lat == city.Lat\n                && e.Lon == city.Lon\n                && e.CountryId == city.CountryId\n                && e.Id != city.Id);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Controllers/CountriesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Dynamic.Core;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CountriesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CountriesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Countries/?pageIndex=0&pageSize=10\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CountryDTO>>> GetCountries(\n        int pageIndex = 0,\n        int pageSize = 10,\n        string sortColumn = null,\n        string sortOrder = null,\n        string filterColumn = null,\n        string filterQuery = null)\n        {\n            return await ApiResult<CountryDTO>.CreateAsync(\n                    _context.Countries\n                        .Select(c => new CountryDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            ISO2 = c.ISO2,\n                            ISO3 = c.ISO3,\n                            TotCities = c.Cities.Count\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        //public async Task<ActionResult<ApiResult<dynamic>>> GetCountries(\n        //    int pageIndex = 0,\n        //    int pageSize = 10,\n        //    string sortColumn = null,\n        //    string sortOrder = null,\n        //    string filterColumn = null,\n        //    string filterQuery = null)\n        //{\n        //    return await ApiResult<dynamic>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n\n        //public async Task<ActionResult<ApiResult<Country>>> GetCountries(\n        //int pageIndex = 0,\n        //int pageSize = 10,\n        //string sortColumn = null,\n        //string sortOrder = null,\n        //string filterColumn = null,\n        //string filterQuery = null)\n        //{\n        //    return await ApiResult<Country>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new Country()\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n        // GET: api/Countries/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<Country>> GetCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            return country;\n        }\n\n        // PUT: api/Countries/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCountry(int id, Country country)\n        {\n            if (id != country.Id)\n            {\n                return BadRequest();\n            }\n\n            _context.Entry(country).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CountryExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Countries\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPost]\n        public async Task<ActionResult<Country>> PostCountry(Country country)\n        {\n            _context.Countries.Add(country);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCountry\", new { id = country.Id }, country);\n        }\n\n        // DELETE: api/Countries/5\n        [Authorize]\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<Country>> DeleteCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            _context.Countries.Remove(country);\n            await _context.SaveChangesAsync();\n\n            return country;\n        }\n\n        private bool CountryExists(int id)\n        {\n            return _context.Countries.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeField\")]\n        public bool IsDupeField(\n            int countryId, \n            string fieldName, \n            string fieldValue)\n        {\n            // Standard approach(using strongly-typed LAMBA expressions)\n            //switch (fieldName)\n            //{\n            //    case \"name\":\n            //        return _context.Countries.Any(\n            //            c => c.Name == fieldValue && c.Id != countryId);\n            //    case \"iso2\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO2 == fieldValue && c.Id != countryId);\n            //    case \"iso3\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO3 == fieldValue && c.Id != countryId);\n            //    default:\n            //        return false;\n            //}\n\n            // Dynamic approach (using System.Linq.Dynamic.Core)\n            return (ApiResult<Country>.IsValidProperty(fieldName, true))\n                ? _context.Countries.Any(\n                    String.Format(\"{0} == @0 && Id != @1\", fieldName),\n                    fieldValue,\n                    countryId)\n                : false;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Controllers/OidcConfigurationController.cs",
    "content": "﻿using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Controllers\n{\n    public class OidcConfigurationController : Controller\n    {\n        private readonly ILogger<OidcConfigurationController> logger;\n\n        public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger<OidcConfigurationController> _logger)\n        {\n            ClientRequestParametersProvider = clientRequestParametersProvider;\n            logger = _logger;\n        }\n\n        public IClientRequestParametersProvider ClientRequestParametersProvider { get; }\n\n        [HttpGet(\"_configuration/{clientId}\")]\n        public IActionResult GetClientRequestParameters([FromRoute]string clientId)\n        {\n            var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);\n            return Ok(parameters);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Controllers/SeedController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing WorldCities.Data;\nusing OfficeOpenXml;\nusing System.IO;\nusing Microsoft.AspNetCore.Hosting;\nusing WorldCities.Data.Models;\nusing System.Text.Json;\nusing Microsoft.AspNetCore.Identity;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]/[action]\")]\n    [ApiController]\n    public class SeedController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n        private readonly RoleManager<IdentityRole> _roleManager;\n        private readonly UserManager<ApplicationUser> _userManager;\n        private readonly IWebHostEnvironment _env;\n\n        public SeedController(\n            ApplicationDbContext context,\n            RoleManager<IdentityRole> roleManager,\n            UserManager<ApplicationUser> userManager,\n            IWebHostEnvironment env)\n        {\n            _context = context;\n            _roleManager = roleManager;\n            _userManager = userManager;\n            _env = env;\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> Import()\n        {\n            // NOTE: This method has been updated on 2020.09.13.\n            // The new version is more efficient than the code described in the book's Chapter 4.\n            // ref.: https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/issues/15\n\n            var path = Path.Combine(\n                _env.ContentRootPath,\n                String.Format(\"Data/Source/worldcities.xlsx\"));\n\n            using (var stream = new FileStream(\n                path,\n                FileMode.Open,\n                FileAccess.Read))\n            {\n                using (var ep = new ExcelPackage(stream))\n                {\n                    // get the first worksheet\n\n                    var ws = ep.Workbook.Worksheets[0];\n\n                    // initialize the record counters\n                    var nCountries = 0;\n                    var nCities = 0;\n\n                    #region Import all Countries\n                    // create a list containing all the countries already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCountries = _context.Countries.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n                        var name = row[nRow, 5].GetValue<string>();\n\n                        // does this country already exist in the database?\n                        if (lstCountries.Where(c => c.Name == name).Count() == 0)\n                        {\n                            // create the Country entity and fill it with xlsx data\n                            var country = new Country();\n                            country.Name = name;\n                            country.ISO2 = row[nRow, 6].GetValue<string>();\n                            country.ISO3 = row[nRow, 7].GetValue<string>();\n\n                            // add the new country to the DB context\n                            _context.Countries.Add(country);\n\n                            // store the country to retrieve its Id later on\n                            lstCountries.Add(country);\n\n                            // increment the counter\n                            nCountries++;\n                        }\n                    }\n\n                    // save all the countries into the Database\n                    if (nCountries > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    #region Import all Cities\n                    // create a list containing all the cities already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCities = _context.Cities.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n\n                        var name = row[nRow, 1].GetValue<string>();\n                        var name_ASCII = row[nRow, 2].GetValue<string>();\n                        var countryName = row[nRow, 5].GetValue<string>();\n                        var lat = row[nRow, 3].GetValue<decimal>();\n                        var lon = row[nRow, 4].GetValue<decimal>();\n                        // retrieve country and countryId\n                        var country = lstCountries.Where(c => c.Name == countryName)\n                            .FirstOrDefault();\n                        var countryId = country.Id;\n\n                        // does this city already exist in the database?\n                        if (lstCities.Where(\n                            c => c.Name == name\n                            && c.Lat == lat\n                            && c.Lon == lon\n                            && c.CountryId == countryId\n                        ).Count() == 0)\n                        {\n                            // create the City entity and fill it with xlsx data\n                            var city = new City();\n                            city.Name = name;\n                            city.Name_ASCII = name_ASCII;\n                            city.Lat = lat;\n                            city.Lon = lon;\n                            city.CountryId = countryId;\n\n                            // add the new city to the DB context\n                            _context.Cities.Add(city);\n\n                            // increment the counter\n                            nCities++;\n                        }\n                    }\n\n                    // save all the cities into the Database\n                    if (nCities > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    return new JsonResult(new\n                    {\n                        Cities = nCities,\n                        Countries = nCountries\n                    });\n                }\n            }\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> CreateDefaultUsers()\n        {\n            // setup the default role names\n            string role_RegisteredUser = \"RegisteredUser\";\n            string role_Administrator = \"Administrator\";\n\n            // create the default roles (if they doesn't exist yet)\n            if (await _roleManager.FindByNameAsync(role_RegisteredUser) == null)\n                await _roleManager.CreateAsync(new IdentityRole(role_RegisteredUser));\n\n            if (await _roleManager.FindByNameAsync(role_Administrator) == null)\n                await _roleManager.CreateAsync(new IdentityRole(role_Administrator));\n\n            // create a list to track the newly added users\n            var addedUserList = new List<ApplicationUser>();\n\n            // check if the admin user already exist\n            var email_Admin = \"admin@email.com\";\n            if (await _userManager.FindByNameAsync(email_Admin) == null)\n            {\n                // create a new admin ApplicationUser account\n                var user_Admin = new ApplicationUser()\n                {\n                    SecurityStamp = Guid.NewGuid().ToString(),\n                    UserName = email_Admin,\n                    Email = email_Admin,\n                };\n\n                // insert the admin user into the DB\n                await _userManager.CreateAsync(user_Admin, \"MySecr3t$\");\n\n                // assign the \"RegisteredUser\" and \"Administrator\" roles\n                await _userManager.AddToRoleAsync(user_Admin, role_RegisteredUser);\n                await _userManager.AddToRoleAsync(user_Admin, role_Administrator);\n\n                // confirm the e-mail and remove lockout\n                user_Admin.EmailConfirmed = true;\n                user_Admin.LockoutEnabled = false;\n\n                // add the admin user to the added users list\n                addedUserList.Add(user_Admin);\n            }\n\n            // check if the standard user already exist\n            var email_User = \"user@email.com\";\n            if (await _userManager.FindByNameAsync(email_User) == null)\n            {\n                // create a new standard ApplicationUser account\n                var user_User = new ApplicationUser()\n                {\n                    SecurityStamp = Guid.NewGuid().ToString(),\n                    UserName = email_User,\n                    Email = email_User\n                };\n\n                // insert the standard user into the DB\n                await _userManager.CreateAsync(user_User, \"MySecr3t$\");\n\n                // assign the \"RegisteredUser\" role\n                await _userManager.AddToRoleAsync(user_User, role_RegisteredUser);\n\n                // confirm the e-mail and remove lockout\n                user_User.EmailConfirmed = true;\n                user_User.LockoutEnabled = false;\n\n                // add the standard user to the added users list\n                addedUserList.Add(user_User);\n            }\n\n            // if we added at least one user, persist the changes into the DB\n            if (addedUserList.Count > 0)\n                await _context.SaveChangesAsync();\n\n            return new JsonResult(new\n            {\n                Count = addedUserList.Count,\n                Users = addedUserList\n            });\n        }\n    }\n}"
  },
  {
    "path": "Chapter_10/WorldCities/Data/ApiResult.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Linq.Dynamic.Core;\nusing System.Reflection;\nusing System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class ApiResult<T>\n    {\n        /// <summary>\n        /// Private constructor called by the CreateAsync method.\n        /// </summary>\n        private ApiResult(\n            List<T> data,\n            int count,\n            int pageIndex,\n            int pageSize,\n            string sortColumn,\n            string sortOrder,\n            string filterColumn,\n            string filterQuery)\n        {\n            Data = data;\n            PageIndex = pageIndex;\n            PageSize = pageSize;\n            TotalCount = count;\n            TotalPages = (int)Math.Ceiling(count / (double)pageSize);\n            SortColumn = sortColumn;\n            SortOrder = sortOrder;\n            FilterColumn = filterColumn;\n            FilterQuery = filterQuery;\n        }\n\n        #region Methods\n        /// <summary>\n        /// Pages, sorts and/or filters a IQueryable source.\n        /// </summary>\n        /// <param name=\"source\">An IQueryable source of generic type</param>\n        /// <param name=\"pageIndex\">Zero-based current page index (0 = first page)</param>\n        /// <param name=\"pageSize\">The actual size of each page</param>\n        /// <param name=\"sortColumn\">The sorting colum name</param>\n        /// <param name=\"sortOrder\">The sorting order (\"ASC\" or \"DESC\")</param>\n        /// <param name=\"filterColumn\">The filtering column name</param>\n        /// <param name=\"filterQuery\">The filtering query (value to lookup)</param>\n        /// <returns>\n        /// A object containing the IQueryable paged/sorted/filtered result \n        /// and all the relevant paging/sorting/filtering navigation info.\n        /// </returns>\n        public static async Task<ApiResult<T>> CreateAsync(\n            IQueryable<T> source,\n            int pageIndex,\n            int pageSize,\n            string sortColumn = null,\n            string sortOrder = null,\n            string filterColumn = null,\n            string filterQuery = null)\n        {\n            if (!String.IsNullOrEmpty(filterColumn)\n                && !String.IsNullOrEmpty(filterQuery)\n                && IsValidProperty(filterColumn))\n            {\n                source = source.Where(\n                    String.Format(\"{0}.Contains(@0)\",\n                    filterColumn),\n                    filterQuery);\n            }\n\n            var count = await source.CountAsync();\n\n            if (!String.IsNullOrEmpty(sortColumn)\n                && IsValidProperty(sortColumn))\n            {\n                sortOrder = !String.IsNullOrEmpty(sortOrder)\n                    && sortOrder.ToUpper() == \"ASC\"\n                    ? \"ASC\"\n                    : \"DESC\";\n                source = source.OrderBy(\n                    String.Format(\n                        \"{0} {1}\",\n                        sortColumn,\n                        sortOrder)\n                    );\n            }\n\n            source = source\n                .Skip(pageIndex * pageSize)\n                .Take(pageSize);\n\n            // retrieve the SQL query (for debug purposes)\n            #if DEBUG\n            {\n                var sql = source.ToSql();\n                // do something with the sql string\n            }\n            #endif\n\n            var data = await source.ToListAsync();\n\n            return new ApiResult<T>(\n                data,\n                count,\n                pageIndex,\n                pageSize,\n                sortColumn,\n                sortOrder,\n                filterColumn,\n                filterQuery);\n        }\n\n        /// <summary>\n        /// Checks if the given property name exists\n        /// to protect against SQL injection attacks\n        /// </summary>\n        public static bool IsValidProperty(\n            string propertyName,\n            bool throwExceptionIfNotFound = true)\n        {\n            var prop = typeof(T).GetProperty(\n                propertyName,\n                BindingFlags.IgnoreCase |\n                BindingFlags.Public |\n                BindingFlags.Static |\n                BindingFlags.Instance);\n            if (prop == null && throwExceptionIfNotFound)\n                throw new NotSupportedException(\n                    String.Format(\n                        \"ERROR: Property '{0}' does not exist.\",\n                        propertyName)\n                    );\n            return prop != null;\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The data result.\n        /// </summary>\n        public List<T> Data { get; private set; }\n\n        /// <summary>\n        /// Zero-based index of current page.\n        /// </summary>\n        public int PageIndex { get; private set; }\n\n        /// <summary>\n        /// Number of items contained in each page.\n        /// </summary>\n        public int PageSize { get; private set; }\n\n        /// <summary>\n        /// Total items count\n        /// </summary>\n        public int TotalCount { get; private set; }\n\n        /// <summary>\n        /// Total pages count\n        /// </summary>\n        public int TotalPages { get; private set; }\n\n        /// <summary>\n        /// TRUE if the current page has a previous page, FALSE otherwise.\n        /// </summary>\n        public bool HasPreviousPage\n        {\n            get\n            {\n                return (PageIndex > 0);\n            }\n        }\n\n        /// <summary>\n        /// TRUE if the current page has a next page, FALSE otherwise.\n        /// </summary>\n        public bool HasNextPage\n        {\n            get\n            {\n                return ((PageIndex +1) < TotalPages);\n            }\n        }\n\n        /// <summary>\n        /// Sorting Column name (or null if none set)\n        /// </summary>\n        public string SortColumn { get; set; }\n\n        /// <summary>\n        /// Sorting Order (\"ASC\", \"DESC\" or null if none set)\n        /// </summary>\n        public string SortOrder { get; set; }\n\n        /// <summary>\n        /// Filter Column name (or null if none set)\n        /// </summary>\n        public string FilterColumn { get; set; }\n\n        /// <summary>\n        /// Filter Query string \n        /// (to be used within the given FilterColumn)\n        /// </summary>\n        public string FilterQuery { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Data/ApplicationDbContext.cs",
    "content": "﻿using IdentityServer4.EntityFramework.Options;\nusing Microsoft.AspNetCore.ApiAuthorization.IdentityServer;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Options;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Data\n{\n    public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>\n    {\n        #region Constructor\n        public ApplicationDbContext(\n            DbContextOptions options,\n            IOptions<OperationalStoreOptions> operationalStoreOptions) \n            : base(options, operationalStoreOptions)\n        {\n        }\n        #endregion Constructor\n\n        #region Methods\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            base.OnModelCreating(modelBuilder);\n\n            // Map Entity names to DB Table names\n            modelBuilder.Entity<City>().ToTable(\"Cities\");\n            modelBuilder.Entity<Country>().ToTable(\"Countries\");\n        }\n        #endregion Methods\n\n        #region Properties\n        public DbSet<City> Cities { get; set; }\n        public DbSet<Country> Countries { get; set; }\n        #endregion Properties\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Data/CityDTO.cs",
    "content": "﻿namespace WorldCities.Data\n{\n    public class CityDTO\n    {\n        public CityDTO() { }\n\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        public string Name_ASCII { get; set; }\n\n        public decimal Lat { get; set; }\n\n        public decimal Lon { get; set; }\n\n        public int CountryId { get; set; }\n\n        public string CountryName { get; set; }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Data/CountryDTO.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class CountryDTO\n    {\n        public CountryDTO() { }\n\n        #region Properties\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n\n        public int TotCities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Data/IQueryableExtensions.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Query;\nusing Microsoft.EntityFrameworkCore.Query.SqlExpressions;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data\n{\n    public static class IQueryableExtension\n    {\n        public static string ToSql<T>(this IQueryable<T> query)\n        {\n            var enumerator = query.Provider\n                .Execute<IEnumerable<T>>(query.Expression).GetEnumerator();\n            var relationalCommandCache = enumerator\n                .Private(\"_relationalCommandCache\");\n            var selectExpression = relationalCommandCache\n                .Private<SelectExpression>(\"_selectExpression\");\n            var factory = relationalCommandCache\n                .Private<IQuerySqlGeneratorFactory>(\"_querySqlGeneratorFactory\");\n\n            var sqlGenerator = factory.Create();\n            var command = sqlGenerator.GetCommand(selectExpression);\n\n            string sql = command.CommandText;\n            return sql;\n        }\n\n        private static object Private(this object obj, string privateField) => \n            obj?.GetType()\n            .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?\n            .GetValue(obj);\n        private static T Private<T>(this object obj, string privateField) => \n            (T)obj?\n            .GetType()\n            .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?\n            .GetValue(obj);\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Data/Migrations/20191230002753_Identity.Designer.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    [Migration(\"20191230002753_Identity\")]\n    partial class Identity\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.1.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.DeviceFlowCodes\", b =>\n                {\n                    b.Property<string>(\"UserCode\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<string>(\"DeviceCode\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .IsRequired()\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.HasKey(\"UserCode\");\n\n                    b.HasIndex(\"DeviceCode\")\n                        .IsUnique();\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.ToTable(\"DeviceCodes\");\n                });\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.PersistedGrant\", b =>\n                {\n                    b.Property<string>(\"Key\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(50)\")\n                        .HasMaxLength(50);\n\n                    b.HasKey(\"Key\");\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.HasIndex(\"SubjectId\", \"ClientId\", \"Type\");\n\n                    b.ToTable(\"PersistedGrants\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRole\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedName\")\n                        .IsUnique()\n                        .HasName(\"RoleNameIndex\")\n                        .HasFilter(\"[NormalizedName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetRoleClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderKey\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderDisplayName\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"LoginProvider\", \"ProviderKey\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserLogins\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"UserId\", \"RoleId\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetUserRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Value\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"UserId\", \"LoginProvider\", \"Name\");\n\n                    b.ToTable(\"AspNetUserTokens\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.ApplicationUser\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<int>(\"AccessFailedCount\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Email\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<bool>(\"EmailConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<bool>(\"LockoutEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<DateTimeOffset?>(\"LockoutEnd\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.Property<string>(\"NormalizedEmail\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedUserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"PasswordHash\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"PhoneNumber\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"PhoneNumberConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"SecurityStamp\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"TwoFactorEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"UserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedEmail\")\n                        .HasName(\"EmailIndex\");\n\n                    b.HasIndex(\"NormalizedUserName\")\n                        .IsUnique()\n                        .HasName(\"UserNameIndex\")\n                        .HasFilter(\"[NormalizedUserName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetUsers\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Data/Migrations/20191230002753_Identity.cs",
    "content": "﻿using System;\nusing Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace WorldCities.Data.Migrations\n{\n    public partial class Identity : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.CreateTable(\n                name: \"AspNetRoles\",\n                columns: table => new\n                {\n                    Id = table.Column<string>(nullable: false),\n                    Name = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedName = table.Column<string>(maxLength: 256, nullable: true),\n                    ConcurrencyStamp = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetRoles\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUsers\",\n                columns: table => new\n                {\n                    Id = table.Column<string>(nullable: false),\n                    UserName = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),\n                    Email = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),\n                    EmailConfirmed = table.Column<bool>(nullable: false),\n                    PasswordHash = table.Column<string>(nullable: true),\n                    SecurityStamp = table.Column<string>(nullable: true),\n                    ConcurrencyStamp = table.Column<string>(nullable: true),\n                    PhoneNumber = table.Column<string>(nullable: true),\n                    PhoneNumberConfirmed = table.Column<bool>(nullable: false),\n                    TwoFactorEnabled = table.Column<bool>(nullable: false),\n                    LockoutEnd = table.Column<DateTimeOffset>(nullable: true),\n                    LockoutEnabled = table.Column<bool>(nullable: false),\n                    AccessFailedCount = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUsers\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"DeviceCodes\",\n                columns: table => new\n                {\n                    UserCode = table.Column<string>(maxLength: 200, nullable: false),\n                    DeviceCode = table.Column<string>(maxLength: 200, nullable: false),\n                    SubjectId = table.Column<string>(maxLength: 200, nullable: true),\n                    ClientId = table.Column<string>(maxLength: 200, nullable: false),\n                    CreationTime = table.Column<DateTime>(nullable: false),\n                    Expiration = table.Column<DateTime>(nullable: false),\n                    Data = table.Column<string>(maxLength: 50000, nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_DeviceCodes\", x => x.UserCode);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"PersistedGrants\",\n                columns: table => new\n                {\n                    Key = table.Column<string>(maxLength: 200, nullable: false),\n                    Type = table.Column<string>(maxLength: 50, nullable: false),\n                    SubjectId = table.Column<string>(maxLength: 200, nullable: true),\n                    ClientId = table.Column<string>(maxLength: 200, nullable: false),\n                    CreationTime = table.Column<DateTime>(nullable: false),\n                    Expiration = table.Column<DateTime>(nullable: true),\n                    Data = table.Column<string>(maxLength: 50000, nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_PersistedGrants\", x => x.Key);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetRoleClaims\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    RoleId = table.Column<string>(nullable: false),\n                    ClaimType = table.Column<string>(nullable: true),\n                    ClaimValue = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetRoleClaims\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_AspNetRoleClaims_AspNetRoles_RoleId\",\n                        column: x => x.RoleId,\n                        principalTable: \"AspNetRoles\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserClaims\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    UserId = table.Column<string>(nullable: false),\n                    ClaimType = table.Column<string>(nullable: true),\n                    ClaimValue = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserClaims\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserClaims_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserLogins\",\n                columns: table => new\n                {\n                    LoginProvider = table.Column<string>(maxLength: 128, nullable: false),\n                    ProviderKey = table.Column<string>(maxLength: 128, nullable: false),\n                    ProviderDisplayName = table.Column<string>(nullable: true),\n                    UserId = table.Column<string>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserLogins\", x => new { x.LoginProvider, x.ProviderKey });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserLogins_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserRoles\",\n                columns: table => new\n                {\n                    UserId = table.Column<string>(nullable: false),\n                    RoleId = table.Column<string>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserRoles\", x => new { x.UserId, x.RoleId });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserRoles_AspNetRoles_RoleId\",\n                        column: x => x.RoleId,\n                        principalTable: \"AspNetRoles\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserRoles_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserTokens\",\n                columns: table => new\n                {\n                    UserId = table.Column<string>(nullable: false),\n                    LoginProvider = table.Column<string>(maxLength: 128, nullable: false),\n                    Name = table.Column<string>(maxLength: 128, nullable: false),\n                    Value = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserTokens\", x => new { x.UserId, x.LoginProvider, x.Name });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserTokens_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetRoleClaims_RoleId\",\n                table: \"AspNetRoleClaims\",\n                column: \"RoleId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"RoleNameIndex\",\n                table: \"AspNetRoles\",\n                column: \"NormalizedName\",\n                unique: true,\n                filter: \"[NormalizedName] IS NOT NULL\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserClaims_UserId\",\n                table: \"AspNetUserClaims\",\n                column: \"UserId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserLogins_UserId\",\n                table: \"AspNetUserLogins\",\n                column: \"UserId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserRoles_RoleId\",\n                table: \"AspNetUserRoles\",\n                column: \"RoleId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"EmailIndex\",\n                table: \"AspNetUsers\",\n                column: \"NormalizedEmail\");\n\n            migrationBuilder.CreateIndex(\n                name: \"UserNameIndex\",\n                table: \"AspNetUsers\",\n                column: \"NormalizedUserName\",\n                unique: true,\n                filter: \"[NormalizedUserName] IS NOT NULL\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_DeviceCodes_DeviceCode\",\n                table: \"DeviceCodes\",\n                column: \"DeviceCode\",\n                unique: true);\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_DeviceCodes_Expiration\",\n                table: \"DeviceCodes\",\n                column: \"Expiration\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_PersistedGrants_Expiration\",\n                table: \"PersistedGrants\",\n                column: \"Expiration\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_PersistedGrants_SubjectId_ClientId_Type\",\n                table: \"PersistedGrants\",\n                columns: new[] { \"SubjectId\", \"ClientId\", \"Type\" });\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.DropTable(\n                name: \"AspNetRoleClaims\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserClaims\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserLogins\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserRoles\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserTokens\");\n\n            migrationBuilder.DropTable(\n                name: \"DeviceCodes\");\n\n            migrationBuilder.DropTable(\n                name: \"PersistedGrants\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetRoles\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUsers\");\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Data/Migrations/ApplicationDbContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    partial class ApplicationDbContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.1.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.DeviceFlowCodes\", b =>\n                {\n                    b.Property<string>(\"UserCode\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<string>(\"DeviceCode\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .IsRequired()\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.HasKey(\"UserCode\");\n\n                    b.HasIndex(\"DeviceCode\")\n                        .IsUnique();\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.ToTable(\"DeviceCodes\");\n                });\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.PersistedGrant\", b =>\n                {\n                    b.Property<string>(\"Key\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(50)\")\n                        .HasMaxLength(50);\n\n                    b.HasKey(\"Key\");\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.HasIndex(\"SubjectId\", \"ClientId\", \"Type\");\n\n                    b.ToTable(\"PersistedGrants\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRole\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedName\")\n                        .IsUnique()\n                        .HasName(\"RoleNameIndex\")\n                        .HasFilter(\"[NormalizedName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetRoleClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderKey\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderDisplayName\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"LoginProvider\", \"ProviderKey\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserLogins\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"UserId\", \"RoleId\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetUserRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Value\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"UserId\", \"LoginProvider\", \"Name\");\n\n                    b.ToTable(\"AspNetUserTokens\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.ApplicationUser\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<int>(\"AccessFailedCount\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Email\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<bool>(\"EmailConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<bool>(\"LockoutEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<DateTimeOffset?>(\"LockoutEnd\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.Property<string>(\"NormalizedEmail\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedUserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"PasswordHash\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"PhoneNumber\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"PhoneNumberConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"SecurityStamp\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"TwoFactorEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"UserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedEmail\")\n                        .HasName(\"EmailIndex\");\n\n                    b.HasIndex(\"NormalizedUserName\")\n                        .IsUnique()\n                        .HasName(\"UserNameIndex\")\n                        .HasFilter(\"[NormalizedUserName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetUsers\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Data/Models/ApplicationUser.cs",
    "content": "﻿using Microsoft.AspNetCore.Identity;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class ApplicationUser : IdentityUser\n    {\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Data/Models/City.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class City\n    {\n        #region Constructor\n        public City()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this City\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// City name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// City name (in ASCII format)\n        /// </summary>\n        public string Name_ASCII { get; set; }\n\n        /// <summary>\n        /// City latitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lat { get; set; }\n\n        /// <summary>\n        /// City longitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lon { get; set; }\n\n        /// <summary>\n        /// Country Id (foreign key)\n        /// </summary>\n        [ForeignKey(\"Country\")]\n        public int CountryId { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// The country related to this city.\n        /// </summary>\n        public virtual Country Country { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Data/Models/Country.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class Country\n    {\n        #region Constructor\n        public Country()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this Country\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// Country name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-2 format)\n        /// </summary>\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-3 format)\n        /// </summary>\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n        #endregion\n\n        #region Client-side properties\n        /// <summary>\n        /// The number of cities related to this country.\n        /// </summary>\n        [NotMapped]\n        public int TotCities\n        {\n            get\n            {\n                return (Cities != null)\n                    ? Cities.Count\n                    : _TotCities;\n            }\n            set { _TotCities = value; }\n        }\n\n        private int _TotCities = 0;\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// A list containing all the cities related to this country.\n        /// </summary>\n        [JsonIgnore]\n        public virtual List<City> Cities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_10/WorldCities/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Pages/Shared/_LoginPartial.cshtml",
    "content": "﻿@using Microsoft.AspNetCore.Identity\n@using WorldCities.Data.Models;\n@inject SignInManager<ApplicationUser> SignInManager\n@inject UserManager<ApplicationUser> UserManager\n\n@{\n    string returnUrl = null;\n    var query = ViewContext.HttpContext.Request.Query;\n    if (query.ContainsKey(\"returnUrl\"))\n    {\n        returnUrl = query[\"returnUrl\"];\n    }\n}\n\n<ul class=\"navbar-nav\">\n    @if (SignInManager.IsSignedIn(User))\n    {\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Manage/Index\" title=\"Manage\">Hello @User.Identity.Name!</a>\n        </li>\n        <li class=\"nav-item\">\n            <form class=\"form-inline\" asp-area=\"Identity\" asp-page=\"/Account/Logout\" asp-route-returnUrl=\"/\">\n                <button type=\"submit\" class=\"nav-link btn btn-link text-dark\">Logout</button>\n            </form>\n        </li>\n    }\n    else\n    {\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Register\" asp-route-returnUrl=\"@returnUrl\">Register</a>\n        </li>\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Login\" asp-route-returnUrl=\"@returnUrl\">Login</a>\n        </li>\n    }\n</ul>\n"
  },
  {
    "path": "Chapter_10/WorldCities/Pages/_ViewImports.cshtml",
    "content": "@using WorldCities\n@namespace WorldCities.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_10/WorldCities/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/Startup.cs",
    "content": "using Microsoft.AspNetCore.Authentication;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing System.Text.Json;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews()\n                .AddJsonOptions(options => {\n            // set this option to TRUE to indent the JSON output\n            options.JsonSerializerOptions.WriteIndented = true;\n            // set this option to NULL to use PascalCase instead of CamelCase (default)\n            // options.JsonSerializerOptions.PropertyNamingPolicy = null;\n        });\n\n\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            // Add EntityFramework support for SqlServer.\n            services.AddEntityFrameworkSqlServer();\n\n            // Add ApplicationDbContext.\n            services.AddDbContext<ApplicationDbContext>(options =>\n                options.UseSqlServer(\n                    Configuration.GetConnectionString(\"DefaultConnection\")\n                    )\n            );\n\n            // Add ASP.NET Core Identity support\n            services.AddDefaultIdentity<ApplicationUser>(options =>\n            {\n                options.SignIn.RequireConfirmedAccount = true;\n                options.Password.RequireLowercase = true;\n                options.Password.RequireUppercase = true;\n                options.Password.RequireDigit = true;\n                options.Password.RequireNonAlphanumeric = true;\n                options.Password.RequiredLength = 8;\n            })\n                .AddRoles<IdentityRole>()\n                .AddEntityFrameworkStores<ApplicationDbContext>();\n\n            services.AddIdentityServer()\n                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();\n\n            services.AddAuthentication()\n                .AddIdentityServerJwt();\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n            app.UseStaticFiles();\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles();\n            }\n\n            app.UseRouting();\n\n            app.UseAuthentication();\n            app.UseIdentityServer();\n            app.UseAuthorization();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n\n                endpoints.MapRazorPages();\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/WorldCities.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"EPPlus\" Version=\"4.5.3.2\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.ApiAuthorization.IdentityServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.UI\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Debug\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" Version=\"3.1.0\" />\n    <PackageReference Include=\"System.Linq.Dynamic.Core\" Version=\"1.0.20\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Folder Include=\"Data\\Migrations\\\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_10/WorldCities/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  },\n  \"IdentityServer\": {\n    \"Key\": {\n      \"Type\": \"Development\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities/appsettings.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"Server=localhost\\\\SQLEXPRESS;Database=WorldCities;User Id=WorldCities;Password=MyVeryOwn$721;Integrated Security=False;MultipleActiveResultSets=True\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"IdentityServer\": {\n    \"Clients\": {\n      \"WorldCities\": {\n        \"Profile\": \"IdentityServerSPA\"\n      }\n    }\n  },\n  \"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities.Tests/CitiesController_Tests.cs",
    "content": "using IdentityServer4.EntityFramework.Options;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Options;\nusing WorldCities.Controllers;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\nusing Xunit;\n\nnamespace WorldCities.Tests\n{\n    public class CitiesController_Tests\n    {\n        /// <summary>\n        /// Test the GetCity() method\n        /// </summary>\n        [Fact]\n        public async void GetCity()\n        {\n            #region Arrange\n            var options = new DbContextOptionsBuilder<ApplicationDbContext>()\n                .UseInMemoryDatabase(databaseName: \"WorldCities\")\n                .Options;\n\n            var storeOptions = Options.Create(new OperationalStoreOptions());\n\n            using (var context = new ApplicationDbContext(options, storeOptions))\n            {\n                context.Add(new City()\n                {\n                    Id = 1,\n                    CountryId = 1,\n                    Lat = 1,\n                    Lon = 1,\n                    Name = \"TestCity1\"\n                });\n                context.SaveChanges();\n            }\n            City city_existing = null;\n            City city_notExisting = null;\n            #endregion\n\n            #region Act\n            using (var context = new ApplicationDbContext(options, storeOptions))\n            {\n                var controller = new CitiesController(context);\n                city_existing = (await controller.GetCity(1)).Value;\n                city_notExisting = (await controller.GetCity(2)).Value;\n            }\n            #endregion\n\n            #region Assert\n            Assert.True(\n                city_existing != null\n                && city_notExisting == null\n                );\n            #endregion\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities.Tests/IdentityHelper.cs",
    "content": "﻿using Microsoft.AspNetCore.Identity;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Options;\nusing Moq;\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace WorldCities.Tests\n{\n    public static class IdentityHelper\n    {\n        public static RoleManager<TIdentityRole> GetRoleManager<TIdentityRole>(\n            IRoleStore<TIdentityRole> roleStore) where TIdentityRole : IdentityRole\n        {\n            return new RoleManager<TIdentityRole>(\n                    roleStore,\n                    new IRoleValidator<TIdentityRole>[0],\n                    new UpperInvariantLookupNormalizer(),\n                    new Mock<IdentityErrorDescriber>().Object,\n                    new Mock<ILogger<RoleManager<TIdentityRole>>>().Object);\n        }\n\n        public static UserManager<TIDentityUser> GetUserManager<TIDentityUser>(\n            IUserStore<TIDentityUser> userStore) where TIDentityUser : IdentityUser\n        {\n            return new UserManager<TIDentityUser>(\n                    userStore,\n                    new Mock<IOptions<IdentityOptions>>().Object,\n                    new Mock<IPasswordHasher<TIDentityUser>>().Object,\n                    new IUserValidator<TIDentityUser>[0],\n                    new IPasswordValidator<TIDentityUser>[0],\n                    new UpperInvariantLookupNormalizer(),\n                    new Mock<IdentityErrorDescriber>().Object,\n                    new Mock<IServiceProvider>().Object,\n                    new Mock<ILogger<UserManager<TIDentityUser>>>().Object);\n        }\n    }\n}"
  },
  {
    "path": "Chapter_10/WorldCities.Tests/SeedController_Tests.cs",
    "content": "using IdentityServer4.EntityFramework.Options;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Identity.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Extensions.Options;\nusing Moq;\nusing System;\nusing WorldCities.Controllers;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\nusing Xunit;\n\nnamespace WorldCities.Tests\n{\n    public class SeedController_Tests\n    {\n        /// <summary>\n        /// Test the CreateDefaultUsers() method\n        /// </summary>\n        [Fact]\n        public async void CreateDefaultUsers()\n        {\n            #region Arrange\n            // create the option instances required by the ApplicationDbContext\n            var options = new DbContextOptionsBuilder<ApplicationDbContext>()\n                .UseInMemoryDatabase(databaseName: \"WorldCities\")\n                .Options;\n            var storeOptions = Options.Create(new OperationalStoreOptions());\n\n            // create a IWebHost environment mock instance\n            var mockEnv = new Mock<IWebHostEnvironment>().Object;\n\n            // define the variables for the users we want to test\n            ApplicationUser user_Admin = null;\n            ApplicationUser user_User = null;\n            ApplicationUser user_NotExisting = null;\n            #endregion\n\n            #region Act\n\n            // create a ApplicationDbContext instance using the in-memory DB\n            using (var context = new ApplicationDbContext(options, storeOptions))\n            {\n                // create a RoleManager instance\n                var roleManager = IdentityHelper.GetRoleManager(\n                    new RoleStore<IdentityRole>(context));\n\n                // create a UserManager instance\n                var userManager = IdentityHelper.GetUserManager(\n                    new UserStore<ApplicationUser>(context));\n\n                // create a SeedController instance\n                var controller = new SeedController(\n                    context,\n                    roleManager,\n                    userManager,\n                    mockEnv\n                    );\n\n                // execute the SeedController's CreateDefaultUsers() method \n                // to create the default users (and roles)\n                await controller.CreateDefaultUsers();\n\n                // retrieve the users\n                user_Admin = await userManager.FindByEmailAsync(\"admin@email.com\");\n                user_User = await userManager.FindByEmailAsync(\"user@email.com\");\n                user_NotExisting = await userManager.FindByEmailAsync(\"notexisting@email.com\");\n            }\n            #endregion\n\n            #region Assert\n            Assert.True(\n                user_Admin != null\n                && user_User != null\n                && user_NotExisting == null\n                );\n            #endregion\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_10/WorldCities.Tests/WorldCities.Tests.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n\n    <IsPackable>false</IsPackable>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.InMemory\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"16.4.0\" />\n    <PackageReference Include=\"Moq\" Version=\"4.13.1\" />\n    <PackageReference Include=\"xunit\" Version=\"2.4.1\" />\n    <PackageReference Include=\"xunit.runner.visualstudio\" Version=\"2.4.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"coverlet.collector\" Version=\"1.2.0\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\WorldCities\\WorldCities.csproj\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "Chapter_10.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_10\\WorldCities\\WorldCities.csproj\", \"{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities.Tests\", \"Chapter_10\\WorldCities.Tests\\WorldCities.Tests.csproj\", \"{C51AB59B-C825-4F43-9EA0-93EBC0BBD69A}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"AuthSample\", \"Chapter_10\\AuthSample\\AuthSample.csproj\", \"{2AFA029B-E93E-47D8-87BD-FEC8A88A0277}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{5DC82B04-93AA-4B83-AE82-759A0E21DFF5}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{C51AB59B-C825-4F43-9EA0-93EBC0BBD69A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{C51AB59B-C825-4F43-9EA0-93EBC0BBD69A}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{C51AB59B-C825-4F43-9EA0-93EBC0BBD69A}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{C51AB59B-C825-4F43-9EA0-93EBC0BBD69A}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{2AFA029B-E93E-47D8-87BD-FEC8A88A0277}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{2AFA029B-E93E-47D8-87BD-FEC8A88A0277}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{2AFA029B-E93E-47D8-87BD-FEC8A88A0277}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{2AFA029B-E93E-47D8-87BD-FEC8A88A0277}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_11/HealthCheck/.config/dotnet-tools.json",
    "content": "{\n  \"version\": 1,\n  \"isRoot\": true,\n  \"tools\": {\n    \"dotnet-ef\": {\n      \"version\": \"3.1.0\",\n      \"commands\": [\n        \"dotnet-ef\"\n      ]\n    }\n  }\n}"
  },
  {
    "path": "Chapter_11/HealthCheck/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/README.md",
    "content": "# HealthCheck\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"HealthCheck\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\n              \"src/assets\",\n              \"src/manifest.webmanifest\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true,\n              \"serviceWorker\": true,\n              \"ngswConfigPath\": \"ngsw-config.json\"\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"HealthCheck:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"HealthCheck:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"HealthCheck:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [ \"src/styles.css\" ],\n            \"scripts\": [],\n            \"assets\": [\n              \"src/assets\",\n              \"src/manifest.webmanifest\"\n            ]\n\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\"src/tsconfig.app.json\", \"src/tsconfig.spec.json\"],\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"HealthCheck-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"HealthCheck:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"HealthCheck\"\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/ngsw-config.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/service-worker/config/schema.json\",\n  \"index\": \"/index.html\",\n  \"assetGroups\": [\n    {\n      \"name\": \"app\",\n      \"installMode\": \"prefetch\",\n      \"resources\": {\n        \"files\": [\n          \"/favicon.ico\",\n          \"/index.html\",\n          \"/manifest.webmanifest\",\n          \"/*.css\",\n          \"/*.js\"\n        ]\n      }\n    },\n    {\n      \"name\": \"assets\",\n      \"installMode\": \"lazy\",\n      \"updateMode\": \"prefetch\",\n      \"resources\": {\n        \"files\": [\n          \"/assets/**\",           \n          \"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)\"\n        ]\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/package.json",
    "content": "{\n  \"name\": \"healthcheck\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run HealthCheck:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@angular/service-worker\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/app.component.html",
    "content": "<body>\n\n  <div class=\"alert alert-warning\" *ngIf=\"!isConnected\">\n    <strong>WARNING</strong>: the app is currently <i>offline</i>:\n    some features that rely upon the back-end might not work as expected.\n    This message will automatically disappear\n    as soon as the internet connection becomes available again.\n  </div>\n\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { ConnectionService } from '../ng-connection-service/connection-service.service';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n\n  hasNetworkConnection: boolean;\n  hasInternetAccess: boolean;\n  isConnected: boolean;\n  status: string;\n\n  constructor(private connectionService: ConnectionService) {\n    this.connectionService.updateOptions({\n      heartbeatUrl: \"/isOnline.txt\"\n    });\n    this.connectionService.monitor().subscribe(currentState => {\n      this.hasNetworkConnection = currentState.hasNetworkConnection;\n      this.hasInternetAccess = currentState.hasInternetAccess;\n      if (this.hasNetworkConnection && this.hasInternetAccess) {\n        this.isConnected = true;\n        this.status = 'ONLINE';\n      }\n      else {\n        this.isConnected = false;\n        this.status = 'OFFLINE';\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\nimport { ServiceWorkerModule } from '@angular/service-worker';\nimport { environment } from '../environments/environment';\n\nimport { AppComponent } from './app.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { HealthCheckComponent } from './health-check/health-check.component';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavMenuComponent,\n    HomeComponent,\n    HealthCheckComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    RouterModule.forRoot([\n      { path: '', component: HomeComponent, pathMatch: 'full' },\n      { path: 'health-check', component: HealthCheckComponent }\n    ]),\n    ServiceWorkerModule.register(\n      'ngsw-worker.js',\n      {\n        registrationStrategy: 'registerImmediately'\n      })\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/health-check/health-check.component.css",
    "content": ".status {\n  font-weight: bold;\n}\n\n.Healthy {\n  color: green;\n}\n\n.Degraded {\n  color: orange;\n}\n\n.Unhealthy {\n  color: red;\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/health-check/health-check.component.html",
    "content": "<h1>Health Check</h1>\n\n<p>Here are the results of our health check:</p>\n\n<p *ngIf=\"!result\"><em>Loading...</em></p>\n\n<table class='table table-striped' aria-labelledby=\"tableLabel\" *ngIf=\"result\">\n  <thead>\n    <tr>\n      <th>Name</th>\n      <th>Response Time</th>\n      <th>Status</th>\n      <th>Description</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr *ngFor=\"let check of result.checks\">\n      <td>{{ check.name }}</td>\n      <td>{{ check.responseTime }}</td>\n      <td class=\"status {{ check.status }}\">{{ check.status }}</td>\n      <td>{{ check.description }}</td>\n    </tr>\n  </tbody>\n</table>\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/health-check/health-check.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\n\n@Component({\n    selector: 'app-health-check',\n    templateUrl: './health-check.component.html',\n    styleUrls: ['./health-check.component.css']\n})\nexport class HealthCheckComponent {\n  public result: Result;\n\n  constructor(\n    private http: HttpClient,\n    @Inject('BASE_URL') private baseUrl: string) {\n  }\n\n  ngOnInit() {\n    this.http.get<Result>(this.baseUrl + 'hc').subscribe(result => {\n      this.result = result;\n    }, error => console.error(error));\n  }\n}\n\ninterface Result {\n    checks: Check[];\n    totalStatus: string;\n    totalResponseTime: number;\n}\n\ninterface Check {\n    name: string;\n    status: string;\n    responseTime: number;\n}\n\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">HealthCheck</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/health-check']\"\n              >Health Check</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>HealthCheck</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n\n    <!-- PWA required files -->\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&amp;display=swap\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n    <link rel=\"manifest\" crossorigin=\"use-credentials\" href=\"manifest.webmanifest\">\n    <meta name=\"theme-color\" content=\"#1976d2\">\n\n  </head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/manifest.webmanifest",
    "content": "{\n  \"name\": \"HealthCheck\",\n  \"short_name\": \"HealthCheck\",\n  \"theme_color\": \"#2196f3\",\n  \"background_color\": \"#2196f3\",\n  \"display\": \"standalone\",\n  \"Scope\": \"/\",\n  \"start_url\": \"/\",\n  \"icons\": [\n    {\n      \"src\": \"assets/icons/icon-72x72.png\",\n      \"sizes\": \"72x72\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-96x96.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-128x128.png\",\n      \"sizes\": \"128x128\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-144x144.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-152x152.png\",\n      \"sizes\": \"152x152\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-384x384.png\",\n      \"sizes\": \"384x384\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"splash_pages\": null\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/ng-connection-service/connection-service.module.ts",
    "content": "import {NgModule} from '@angular/core';\nimport {ConnectionService} from './connection-service.service';\nimport {HttpClientModule} from '@angular/common/http';\n\n@NgModule({\n  imports: [HttpClientModule],\n  providers: [ConnectionService]\n})\nexport class ConnectionServiceModule {\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/ng-connection-service/connection-service.service.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { ConnectionService } from './connection-service.service';\n\ndescribe('ConnectionServiceService', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [ConnectionService]\n    });\n  });\n\n  it('should be created', inject([ConnectionService], (service: ConnectionService) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/ng-connection-service/connection-service.service.ts",
    "content": "import {EventEmitter, Inject, Injectable, InjectionToken, OnDestroy, Optional} from '@angular/core';\nimport {fromEvent, Observable, Subscription, timer} from 'rxjs';\nimport {debounceTime, delay, retryWhen, startWith, switchMap, tap} from 'rxjs/operators';\nimport {HttpClient} from '@angular/common/http';\nimport * as _ from 'lodash';\n\n/**\n * Instance of this interface is used to report current connection status.\n */\nexport interface ConnectionState {\n  /**\n   * \"True\" if browser has network connection. Determined by Window objects \"online\" / \"offline\" events.\n   */\n  hasNetworkConnection: boolean;\n  /**\n   * \"True\" if browser has Internet access. Determined by heartbeat system which periodically makes request to heartbeat Url.\n   */\n  hasInternetAccess: boolean;\n}\n\n/**\n * Instance of this interface could be used to configure \"ConnectionService\".\n */\nexport interface ConnectionServiceOptions {\n  /**\n   * Controls the Internet connectivity heartbeat system. Default value is 'true'.\n   */\n  enableHeartbeat?: boolean;\n  /**\n   * Url used for checking Internet connectivity, heartbeat system periodically makes \"HEAD\" requests to this URL to determine Internet\n   * connection status. Default value is \"//internethealthtest.org\".\n   */\n  heartbeatUrl?: string;\n  /**\n   * Interval used to check Internet connectivity specified in milliseconds. Default value is \"30000\".\n   */\n  heartbeatInterval?: number;\n  /**\n   * Interval used to retry Internet connectivity checks when an error is detected (when no Internet connection). Default value is \"1000\".\n   */\n  heartbeatRetryInterval?: number;\n  /**\n   * HTTP method used for requesting heartbeat Url. Default is 'head'.\n   */\n  requestMethod?: 'get' | 'post' | 'head' | 'options';\n\n}\n\n/**\n * InjectionToken for specifing ConnectionService options.\n */\nexport const ConnectionServiceOptionsToken: InjectionToken<ConnectionServiceOptions> = new InjectionToken('ConnectionServiceOptionsToken');\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class ConnectionService implements OnDestroy {\n  private static DEFAULT_OPTIONS: ConnectionServiceOptions = {\n    enableHeartbeat: true,\n    heartbeatUrl: '//internethealthtest.org',\n    heartbeatInterval: 30000,\n    heartbeatRetryInterval: 1000,\n    requestMethod: 'head'\n  };\n\n  private stateChangeEventEmitter = new EventEmitter<ConnectionState>();\n\n  private currentState: ConnectionState = {\n    hasInternetAccess: false,\n    hasNetworkConnection: window.navigator.onLine\n  };\n  private offlineSubscription: Subscription;\n  private onlineSubscription: Subscription;\n  private httpSubscription: Subscription;\n  private serviceOptions: ConnectionServiceOptions;\n\n  /**\n   * Current ConnectionService options. Notice that changing values of the returned object has not effect on service execution.\n   * You should use \"updateOptions\" function.\n   */\n  get options(): ConnectionServiceOptions {\n    return _.clone(this.serviceOptions);\n  }\n\n  constructor(private http: HttpClient, @Inject(ConnectionServiceOptionsToken) @Optional() options: ConnectionServiceOptions) {\n    this.serviceOptions = _.defaults({}, options, ConnectionService.DEFAULT_OPTIONS);\n\n    this.checkNetworkState();\n    this.checkInternetState();\n  }\n\n  private checkInternetState() {\n\n    if (!_.isNil(this.httpSubscription)) {\n      this.httpSubscription.unsubscribe();\n    }\n\n    if (this.serviceOptions.enableHeartbeat) {\n      this.httpSubscription = timer(0, this.serviceOptions.heartbeatInterval)\n        .pipe(\n          switchMap(() => this.http[this.serviceOptions.requestMethod](this.serviceOptions.heartbeatUrl, {responseType: 'text'})),\n          retryWhen(errors =>\n            errors.pipe(\n              // log error message\n              tap(val => {\n                console.error('Http error:', val);\n                this.currentState.hasInternetAccess = false;\n                this.emitEvent();\n              }),\n              // restart after 5 seconds\n              delay(this.serviceOptions.heartbeatRetryInterval)\n            )\n          )\n        )\n        .subscribe(result => {\n          this.currentState.hasInternetAccess = true;\n          this.emitEvent();\n        });\n    } else {\n      this.currentState.hasInternetAccess = false;\n      this.emitEvent();\n    }\n  }\n\n  private checkNetworkState() {\n    this.onlineSubscription = fromEvent(window, 'online').subscribe(() => {\n      this.currentState.hasNetworkConnection = true;\n      this.checkInternetState();\n      this.emitEvent();\n    });\n\n    this.offlineSubscription = fromEvent(window, 'offline').subscribe(() => {\n      this.currentState.hasNetworkConnection = false;\n      this.checkInternetState();\n      this.emitEvent();\n    });\n  }\n\n  private emitEvent() {\n    this.stateChangeEventEmitter.emit(this.currentState);\n  }\n\n  ngOnDestroy(): void {\n    try {\n      this.offlineSubscription.unsubscribe();\n      this.onlineSubscription.unsubscribe();\n      this.httpSubscription.unsubscribe();\n    } catch (e) {\n    }\n  }\n\n  /**\n   * Monitor Network & Internet connection status by subscribing to this observer. If you set \"reportCurrentState\" to \"false\" then\n   * function will not report current status of the connections when initially subscribed.\n   * @param reportCurrentState Report current state when initial subscription. Default is \"true\"\n   */\n  monitor(reportCurrentState = true): Observable<ConnectionState> {\n    return reportCurrentState ?\n      this.stateChangeEventEmitter.pipe(\n        debounceTime(300),\n        startWith(this.currentState),\n      )\n      :\n      this.stateChangeEventEmitter.pipe(\n        debounceTime(300)\n      );\n  }\n\n  /**\n   * Update options of the service. You could specify partial options object. Values that are not specified will use default / previous\n   * option values.\n   * @param options Partial option values.\n   */\n  updateOptions(options: Partial<ConnectionServiceOptions>) {\n    this.serviceOptions = _.defaults({}, options, this.serviceOptions);\n    this.checkInternetState();\n  }\n\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  },\n  \"angularCompilerOptions\": {\n    \"strictMetadataEmit\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_11/HealthCheck/CustomHealthCheckOptions.cs",
    "content": "﻿using Microsoft.AspNetCore.Diagnostics.HealthChecks;\nusing Microsoft.AspNetCore.Http;\nusing System.Linq;\nusing System.Net.Mime;\nusing System.Text.Json;\n\nnamespace HealthCheck\n{\n    public class CustomHealthCheckOptions : HealthCheckOptions\n    {\n        public CustomHealthCheckOptions() : base() \n        {\n            var jsonSerializerOptions = new JsonSerializerOptions() \n            { \n                WriteIndented = true \n            };\n\n            ResponseWriter = async (c, r) =>\n            {\n                c.Response.ContentType = MediaTypeNames.Application.Json;\n                c.Response.StatusCode = StatusCodes.Status200OK;\n\n                var result = JsonSerializer.Serialize(new\n                   {\n                      checks = r.Entries.Select(e => new\n                          {\n                              name = e.Key,\n                              responseTime = e.Value.Duration.TotalMilliseconds,\n                              status = e.Value.Status.ToString(),\n                              description = e.Value.Description\n                          }),\n                      totalStatus = r.Status,\n                      totalResponseTime = r.TotalDuration.TotalMilliseconds,\n                   }, jsonSerializerOptions);\n                await c.Response.WriteAsync(result);\n            };\n        }\n    }\n}"
  },
  {
    "path": "Chapter_11/HealthCheck/HealthCheck.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n    <RootNamespace>HealthCheck</RootNamespace>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Folder Include=\"Controllers\\\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Content Update=\"wwwroot\\favicon.ico\">\n      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n    <Content Update=\"wwwroot\\isOnline.txt\">\n      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_11/HealthCheck/ICMPHealthCheck.cs",
    "content": "﻿using Microsoft.Extensions.Diagnostics.HealthChecks;\nusing System;\nusing System.Net.NetworkInformation;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace HealthCheck\n{\n    public class ICMPHealthCheck : IHealthCheck\n    {\n        private string Host { get; set; }\n        private int Timeout { get; set; }\n\n        public ICMPHealthCheck(string host, int timeout)\n        {\n            Host = host;\n            Timeout = timeout;\n        }\n\n        public async Task<HealthCheckResult> CheckHealthAsync(\n            HealthCheckContext context,\n            CancellationToken cancellationToken = default)\n        {\n            try\n            {\n                using (var ping = new Ping())\n                {\n                    var reply = await ping.SendPingAsync(Host);\n\n                    switch (reply.Status)\n                    {\n                        case IPStatus.Success:\n                            var msg = String.Format(\n                                \"IMCP to {0} took {1} ms.\",\n                                Host,\n                                reply.RoundtripTime);\n\n                            return (reply.RoundtripTime > Timeout)\n                                ? HealthCheckResult.Degraded(msg)\n                                : HealthCheckResult.Healthy(msg);\n\n                        default:\n                            var err = String.Format(\n                                \"IMCP to {0} failed: {1}\",\n                                Host,\n                                reply.Status);\n                            return HealthCheckResult.Unhealthy(err);\n                    }\n                }\n            }\n            catch (Exception e)\n            {\n                var err = String.Format(\n                    \"IMCP to {0} failed: {1}\",\n                    Host,\n                    e.Message);\n                return HealthCheckResult.Unhealthy(err);\n            }\n        }\n    }\n}"
  },
  {
    "path": "Chapter_11/HealthCheck/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_11/HealthCheck/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace HealthCheck.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/Pages/_ViewImports.cshtml",
    "content": "@using HealthCheck\n@namespace HealthCheck.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_11/HealthCheck/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace HealthCheck\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.AspNetCore.StaticFiles;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace HealthCheck\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews();\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            services.AddHealthChecks()\n                .AddCheck(\"ICMP_01\", new ICMPHealthCheck(\"www.ryadel.com\", 100))\n                .AddCheck(\"ICMP_02\", new ICMPHealthCheck(\"www.google.com\", 100))\n                .AddCheck(\"ICMP_03\", new ICMPHealthCheck(\"www.does-not-exist.com\", 100));\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n\n            app.UseHttpsRedirection();\n\n            // add .webmanifest MIME-type support\n            FileExtensionContentTypeProvider provider = new FileExtensionContentTypeProvider();\n            provider.Mappings[\".webmanifest\"] = \"application/manifest+json\";\n\n            app.UseStaticFiles(new StaticFileOptions()\n            {\n                ContentTypeProvider = provider,\n                OnPrepareResponse = (context) =>\n                {\n                    if (context.File.Name == \"isOnline.txt\")\n                    {\n                        // disable caching for these files\n                        context.Context.Response.Headers.Add(\"Cache-Control\", \"no-cache, no-store\");\n                        context.Context.Response.Headers.Add(\"Expires\", \"-1\");\n                    }\n                    else\n                    {\n                        // Retrieve cache configuration from appsettings.json\n                        context.Context.Response.Headers[\"Cache-Control\"] =\n                            Configuration[\"StaticFiles:Headers:Cache-Control\"];\n                        context.Context.Response.Headers[\"Pragma\"] =\n                            Configuration[\"StaticFiles:Headers:Pragma\"];\n                        context.Context.Response.Headers[\"Expires\"] =\n                            Configuration[\"StaticFiles:Headers:Expires\"];\n                    }\n                }\n            });\n\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles(new StaticFileOptions()\n                {\n                    ContentTypeProvider = provider\n                });\n            }\n\n            app.UseRouting();\n\n            app.UseHealthChecks(\"/hc\", new CustomHealthCheckOptions());\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  },\n  \"StaticFiles\": {\n    \"Headers\": {\n      \"Cache-Control\": \"no-cache, no-store\",\n      \"Pragma\": \"no-cache\",\n      \"Expires\": \"-1\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/appsettings.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\",\n  \"StaticFiles\": {\n    \"Headers\": {\n      \"Cache-Control\": \"max-age=3600\",\n      \"Pragma\": \"cache\",\n      \"Expires\": null\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/libman.json",
    "content": "{\n  \"version\": \"1.0\",\n  \"defaultProvider\": \"cdnjs\",\n  \"libraries\": []\n}"
  },
  {
    "path": "Chapter_11/HealthCheck/wwwroot/isOnline.txt",
    "content": "﻿."
  },
  {
    "path": "Chapter_11/HealthCheck/wwwroot/manifest.webmanifest",
    "content": "{\n  \"name\": \"HealthCheck\",\n  \"short_name\": \"HealthCheck\",\n  \"theme_color\": \"#2196f3\",\n  \"background_color\": \"#2196f3\",\n  \"display\": \"standalone\",\n  \"Scope\": \"/\",\n  \"start_url\": \"/\",\n  \"icons\": [\n    {\n      \"src\": \"assets/icons/icon-72x72.png\",\n      \"sizes\": \"72x72\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-96x96.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-128x128.png\",\n      \"sizes\": \"128x128\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-144x144.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-152x152.png\",\n      \"sizes\": \"152x152\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-384x384.png\",\n      \"sizes\": \"384x384\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"splash_pages\": null\n}\n"
  },
  {
    "path": "Chapter_11/HealthCheck/wwwroot/ngsw-worker.js",
    "content": "(function () {\n    'use strict';\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * Adapts the service worker to its runtime environment.\n     *\n     * Mostly, this is used to mock out identifiers which are otherwise read\n     * from the global scope.\n     */\n    class Adapter {\n        constructor(scope) {\n            // Suffixing `ngsw` with the baseHref to avoid clash of cache names\n            // for SWs with different scopes on the same domain.\n            const baseHref = this.parseUrl(scope.registration.scope).path;\n            this.cacheNamePrefix = 'ngsw:' + baseHref;\n        }\n        /**\n         * Wrapper around the `Request` constructor.\n         */\n        newRequest(input, init) {\n            return new Request(input, init);\n        }\n        /**\n         * Wrapper around the `Response` constructor.\n         */\n        newResponse(body, init) { return new Response(body, init); }\n        /**\n         * Wrapper around the `Headers` constructor.\n         */\n        newHeaders(headers) { return new Headers(headers); }\n        /**\n         * Test if a given object is an instance of `Client`.\n         */\n        isClient(source) { return (source instanceof Client); }\n        /**\n         * Read the current UNIX time in milliseconds.\n         */\n        get time() { return Date.now(); }\n        /**\n         * Extract the pathname of a URL.\n         */\n        parseUrl(url, relativeTo) {\n            // Workaround a Safari bug, see\n            // https://github.com/angular/angular/issues/31061#issuecomment-503637978\n            const parsed = !relativeTo ? new URL(url) : new URL(url, relativeTo);\n            return { origin: parsed.origin, path: parsed.pathname, search: parsed.search };\n        }\n        /**\n         * Wait for a given amount of time before completing a Promise.\n         */\n        timeout(ms) {\n            return new Promise(resolve => { setTimeout(() => resolve(), ms); });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * An error returned in rejected promises if the given key is not found in the table.\n     */\n    class NotFound {\n        constructor(table, key) {\n            this.table = table;\n            this.key = key;\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * An implementation of a `Database` that uses the `CacheStorage` API to serialize\n     * state within mock `Response` objects.\n     */\n    class CacheDatabase {\n        constructor(scope, adapter) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.tables = new Map();\n        }\n        'delete'(name) {\n            if (this.tables.has(name)) {\n                this.tables.delete(name);\n            }\n            return this.scope.caches.delete(`${this.adapter.cacheNamePrefix}:db:${name}`);\n        }\n        list() {\n            return this.scope.caches.keys().then(keys => keys.filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:db:`)));\n        }\n        open(name) {\n            if (!this.tables.has(name)) {\n                const table = this.scope.caches.open(`${this.adapter.cacheNamePrefix}:db:${name}`)\n                    .then(cache => new CacheTable(name, cache, this.adapter));\n                this.tables.set(name, table);\n            }\n            return this.tables.get(name);\n        }\n    }\n    /**\n     * A `Table` backed by a `Cache`.\n     */\n    class CacheTable {\n        constructor(table, cache, adapter) {\n            this.table = table;\n            this.cache = cache;\n            this.adapter = adapter;\n        }\n        request(key) { return this.adapter.newRequest('/' + key); }\n        'delete'(key) { return this.cache.delete(this.request(key)); }\n        keys() {\n            return this.cache.keys().then(requests => requests.map(req => req.url.substr(1)));\n        }\n        read(key) {\n            return this.cache.match(this.request(key)).then(res => {\n                if (res === undefined) {\n                    return Promise.reject(new NotFound(this.table, key));\n                }\n                return res.json();\n            });\n        }\n        write(key, value) {\n            return this.cache.put(this.request(key), this.adapter.newResponse(JSON.stringify(value)));\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var UpdateCacheStatus;\n    (function (UpdateCacheStatus) {\n        UpdateCacheStatus[UpdateCacheStatus[\"NOT_CACHED\"] = 0] = \"NOT_CACHED\";\n        UpdateCacheStatus[UpdateCacheStatus[\"CACHED_BUT_UNUSED\"] = 1] = \"CACHED_BUT_UNUSED\";\n        UpdateCacheStatus[UpdateCacheStatus[\"CACHED\"] = 2] = \"CACHED\";\n    })(UpdateCacheStatus || (UpdateCacheStatus = {}));\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    class SwCriticalError extends Error {\n        constructor() {\n            super(...arguments);\n            this.isCritical = true;\n        }\n    }\n    function errorToString(error) {\n        if (error instanceof Error) {\n            return `${error.message}\\n${error.stack}`;\n        }\n        else {\n            return `${error}`;\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * Compute the SHA1 of the given string\n     *\n     * see http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf\n     *\n     * WARNING: this function has not been designed not tested with security in mind.\n     *          DO NOT USE IT IN A SECURITY SENSITIVE CONTEXT.\n     *\n     * Borrowed from @angular/compiler/src/i18n/digest.ts\n     */\n    function sha1(str) {\n        const utf8 = str;\n        const words32 = stringToWords32(utf8, Endian.Big);\n        return _sha1(words32, utf8.length * 8);\n    }\n    function sha1Binary(buffer) {\n        const words32 = arrayBufferToWords32(buffer, Endian.Big);\n        return _sha1(words32, buffer.byteLength * 8);\n    }\n    function _sha1(words32, len) {\n        const w = [];\n        let [a, b, c, d, e] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0];\n        words32[len >> 5] |= 0x80 << (24 - len % 32);\n        words32[((len + 64 >> 9) << 4) + 15] = len;\n        for (let i = 0; i < words32.length; i += 16) {\n            const [h0, h1, h2, h3, h4] = [a, b, c, d, e];\n            for (let j = 0; j < 80; j++) {\n                if (j < 16) {\n                    w[j] = words32[i + j];\n                }\n                else {\n                    w[j] = rol32(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);\n                }\n                const [f, k] = fk(j, b, c, d);\n                const temp = [rol32(a, 5), f, e, k, w[j]].reduce(add32);\n                [e, d, c, b, a] = [d, c, rol32(b, 30), a, temp];\n            }\n            [a, b, c, d, e] = [add32(a, h0), add32(b, h1), add32(c, h2), add32(d, h3), add32(e, h4)];\n        }\n        return byteStringToHexString(words32ToByteString([a, b, c, d, e]));\n    }\n    function add32(a, b) {\n        return add32to64(a, b)[1];\n    }\n    function add32to64(a, b) {\n        const low = (a & 0xffff) + (b & 0xffff);\n        const high = (a >>> 16) + (b >>> 16) + (low >>> 16);\n        return [high >>> 16, (high << 16) | (low & 0xffff)];\n    }\n    // Rotate a 32b number left `count` position\n    function rol32(a, count) {\n        return (a << count) | (a >>> (32 - count));\n    }\n    var Endian;\n    (function (Endian) {\n        Endian[Endian[\"Little\"] = 0] = \"Little\";\n        Endian[Endian[\"Big\"] = 1] = \"Big\";\n    })(Endian || (Endian = {}));\n    function fk(index, b, c, d) {\n        if (index < 20) {\n            return [(b & c) | (~b & d), 0x5a827999];\n        }\n        if (index < 40) {\n            return [b ^ c ^ d, 0x6ed9eba1];\n        }\n        if (index < 60) {\n            return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];\n        }\n        return [b ^ c ^ d, 0xca62c1d6];\n    }\n    function stringToWords32(str, endian) {\n        const size = (str.length + 3) >>> 2;\n        const words32 = [];\n        for (let i = 0; i < size; i++) {\n            words32[i] = wordAt(str, i * 4, endian);\n        }\n        return words32;\n    }\n    function arrayBufferToWords32(buffer, endian) {\n        const size = (buffer.byteLength + 3) >>> 2;\n        const words32 = [];\n        const view = new Uint8Array(buffer);\n        for (let i = 0; i < size; i++) {\n            words32[i] = wordAt(view, i * 4, endian);\n        }\n        return words32;\n    }\n    function byteAt(str, index) {\n        if (typeof str === 'string') {\n            return index >= str.length ? 0 : str.charCodeAt(index) & 0xff;\n        }\n        else {\n            return index >= str.byteLength ? 0 : str[index] & 0xff;\n        }\n    }\n    function wordAt(str, index, endian) {\n        let word = 0;\n        if (endian === Endian.Big) {\n            for (let i = 0; i < 4; i++) {\n                word += byteAt(str, index + i) << (24 - 8 * i);\n            }\n        }\n        else {\n            for (let i = 0; i < 4; i++) {\n                word += byteAt(str, index + i) << 8 * i;\n            }\n        }\n        return word;\n    }\n    function words32ToByteString(words32) {\n        return words32.reduce((str, word) => str + word32ToByteString(word), '');\n    }\n    function word32ToByteString(word) {\n        let str = '';\n        for (let i = 0; i < 4; i++) {\n            str += String.fromCharCode((word >>> 8 * (3 - i)) & 0xff);\n        }\n        return str;\n    }\n    function byteStringToHexString(str) {\n        let hex = '';\n        for (let i = 0; i < str.length; i++) {\n            const b = byteAt(str, i);\n            hex += (b >>> 4).toString(16) + (b & 0x0f).toString(16);\n        }\n        return hex.toLowerCase();\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    /**\n     * A group of assets that are cached in a `Cache` and managed by a given policy.\n     *\n     * Concrete classes derive from this base and specify the exact caching policy.\n     */\n    class AssetGroup {\n        constructor(scope, adapter, idle, config, hashes, db, prefix) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.idle = idle;\n            this.config = config;\n            this.hashes = hashes;\n            this.db = db;\n            this.prefix = prefix;\n            /**\n             * A deduplication cache, to make sure the SW never makes two network requests\n             * for the same resource at once. Managed by `fetchAndCacheOnce`.\n             */\n            this.inFlightRequests = new Map();\n            /**\n             * Regular expression patterns.\n             */\n            this.patterns = [];\n            this.name = config.name;\n            // Patterns in the config are regular expressions disguised as strings. Breathe life into them.\n            this.patterns = this.config.patterns.map(pattern => new RegExp(pattern));\n            // This is the primary cache, which holds all of the cached requests for this group. If a\n            // resource\n            // isn't in this cache, it hasn't been fetched yet.\n            this.cache = this.scope.caches.open(`${this.prefix}:${this.config.name}:cache`);\n            // This is the metadata table, which holds specific information for each cached URL, such as\n            // the timestamp of when it was added to the cache.\n            this.metadata = this.db.open(`${this.prefix}:${this.config.name}:meta`);\n            // Determine the origin from the registration scope. This is used to differentiate between\n            // relative and absolute URLs.\n            this.origin = this.adapter.parseUrl(this.scope.registration.scope).origin;\n        }\n        cacheStatus(url) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                const meta = yield this.metadata;\n                const res = yield cache.match(this.adapter.newRequest(url));\n                if (res === undefined) {\n                    return UpdateCacheStatus.NOT_CACHED;\n                }\n                try {\n                    const data = yield meta.read(url);\n                    if (!data.used) {\n                        return UpdateCacheStatus.CACHED_BUT_UNUSED;\n                    }\n                }\n                catch (_) {\n                    // Error on the side of safety and assume cached.\n                }\n                return UpdateCacheStatus.CACHED;\n            });\n        }\n        /**\n         * Clean up all the cached data for this group.\n         */\n        cleanup() {\n            return __awaiter(this, void 0, void 0, function* () {\n                yield this.scope.caches.delete(`${this.prefix}:${this.config.name}:cache`);\n                yield this.db.delete(`${this.prefix}:${this.config.name}:meta`);\n            });\n        }\n        /**\n         * Process a request for a given resource and return it, or return null if it's not available.\n         */\n        handleFetch(req, ctx) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                // Either the request matches one of the known resource URLs, one of the patterns for\n                // dynamically matched URLs, or neither. Determine which is the case for this request in\n                // order to decide how to handle it.\n                if (this.config.urls.indexOf(url) !== -1 || this.patterns.some(pattern => pattern.test(url))) {\n                    // This URL matches a known resource. Either it's been cached already or it's missing, in\n                    // which case it needs to be loaded from the network.\n                    // Open the cache to check whether this resource is present.\n                    const cache = yield this.cache;\n                    // Look for a cached response. If one exists, it can be used to resolve the fetch\n                    // operation.\n                    const cachedResponse = yield cache.match(req);\n                    if (cachedResponse !== undefined) {\n                        // A response has already been cached (which presumably matches the hash for this\n                        // resource). Check whether it's safe to serve this resource from cache.\n                        if (this.hashes.has(url)) {\n                            // This resource has a hash, and thus is versioned by the manifest. It's safe to return\n                            // the response.\n                            return cachedResponse;\n                        }\n                        else {\n                            // This resource has no hash, and yet exists in the cache. Check how old this request is\n                            // to make sure it's still usable.\n                            if (yield this.needToRevalidate(req, cachedResponse)) {\n                                this.idle.schedule(`revalidate(${this.prefix}, ${this.config.name}): ${req.url}`, () => __awaiter(this, void 0, void 0, function* () { yield this.fetchAndCacheOnce(req); }));\n                            }\n                            // In either case (revalidation or not), the cached response must be good.\n                            return cachedResponse;\n                        }\n                    }\n                    // No already-cached response exists, so attempt a fetch/cache operation. The original request\n                    // may specify things like credential inclusion, but for assets these are not honored in order\n                    // to avoid issues with opaque responses. The SW requests the data itself.\n                    const res = yield this.fetchAndCacheOnce(this.adapter.newRequest(req.url));\n                    // If this is successful, the response needs to be cloned as it might be used to respond to\n                    // multiple fetch operations at the same time.\n                    return res.clone();\n                }\n                else {\n                    return null;\n                }\n            });\n        }\n        getConfigUrl(url) {\n            // If the URL is relative to the SW's own origin, then only consider the path relative to\n            // the domain root. Determine this by checking the URL's origin against the SW's.\n            const parsed = this.adapter.parseUrl(url, this.scope.registration.scope);\n            if (parsed.origin === this.origin) {\n                // The URL is relative to the SW's origin domain.\n                return parsed.path;\n            }\n            else {\n                return url;\n            }\n        }\n        /**\n         * Some resources are cached without a hash, meaning that their expiration is controlled\n         * by HTTP caching headers. Check whether the given request/response pair is still valid\n         * per the caching headers.\n         */\n        needToRevalidate(req, res) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Three different strategies apply here:\n                // 1) The request has a Cache-Control header, and thus expiration needs to be based on its age.\n                // 2) The request has an Expires header, and expiration is based on the current timestamp.\n                // 3) The request has no applicable caching headers, and must be revalidated.\n                if (res.headers.has('Cache-Control')) {\n                    // Figure out if there is a max-age directive in the Cache-Control header.\n                    const cacheControl = res.headers.get('Cache-Control');\n                    const cacheDirectives = cacheControl\n                        // Directives are comma-separated within the Cache-Control header value.\n                        .split(',')\n                        // Make sure each directive doesn't have extraneous whitespace.\n                        .map(v => v.trim())\n                        // Some directives have values (like maxage and s-maxage)\n                        .map(v => v.split('='));\n                    // Lowercase all the directive names.\n                    cacheDirectives.forEach(v => v[0] = v[0].toLowerCase());\n                    // Find the max-age directive, if one exists.\n                    const maxAgeDirective = cacheDirectives.find(v => v[0] === 'max-age');\n                    const cacheAge = maxAgeDirective ? maxAgeDirective[1] : undefined;\n                    if (!cacheAge) {\n                        // No usable TTL defined. Must assume that the response is stale.\n                        return true;\n                    }\n                    try {\n                        const maxAge = 1000 * parseInt(cacheAge);\n                        // Determine the origin time of this request. If the SW has metadata on the request (which\n                        // it\n                        // should), it will have the time the request was added to the cache. If it doesn't for some\n                        // reason, the request may have a Date header which will serve the same purpose.\n                        let ts;\n                        try {\n                            // Check the metadata table. If a timestamp is there, use it.\n                            const metaTable = yield this.metadata;\n                            ts = (yield metaTable.read(req.url)).ts;\n                        }\n                        catch (_a) {\n                            // Otherwise, look for a Date header.\n                            const date = res.headers.get('Date');\n                            if (date === null) {\n                                // Unable to determine when this response was created. Assume that it's stale, and\n                                // revalidate it.\n                                return true;\n                            }\n                            ts = Date.parse(date);\n                        }\n                        const age = this.adapter.time - ts;\n                        return age < 0 || age > maxAge;\n                    }\n                    catch (_b) {\n                        // Assume stale.\n                        return true;\n                    }\n                }\n                else if (res.headers.has('Expires')) {\n                    // Determine if the expiration time has passed.\n                    const expiresStr = res.headers.get('Expires');\n                    try {\n                        // The request needs to be revalidated if the current time is later than the expiration\n                        // time, if it parses correctly.\n                        return this.adapter.time > Date.parse(expiresStr);\n                    }\n                    catch (_c) {\n                        // The expiration date failed to parse, so revalidate as a precaution.\n                        return true;\n                    }\n                }\n                else {\n                    // No way to evaluate staleness, so assume the response is already stale.\n                    return true;\n                }\n            });\n        }\n        /**\n         * Fetch the complete state of a cached resource, or return null if it's not found.\n         */\n        fetchFromCacheOnly(url) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                const metaTable = yield this.metadata;\n                // Lookup the response in the cache.\n                const response = yield cache.match(this.adapter.newRequest(url));\n                if (response === undefined) {\n                    // It's not found, return null.\n                    return null;\n                }\n                // Next, lookup the cached metadata.\n                let metadata = undefined;\n                try {\n                    metadata = yield metaTable.read(url);\n                }\n                catch (_a) {\n                    // Do nothing, not found. This shouldn't happen, but it can be handled.\n                }\n                // Return both the response and any available metadata.\n                return { response, metadata };\n            });\n        }\n        /**\n         * Lookup all resources currently stored in the cache which have no associated hash.\n         */\n        unhashedResources() {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                // Start with the set of all cached URLs.\n                return (yield cache.keys())\n                    .map(request => request.url)\n                    // Exclude the URLs which have hashes.\n                    .filter(url => !this.hashes.has(url));\n            });\n        }\n        /**\n         * Fetch the given resource from the network, and cache it if able.\n         */\n        fetchAndCacheOnce(req, used = true) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // The `inFlightRequests` map holds information about which caching operations are currently\n                // underway for known resources. If this request appears there, another \"thread\" is already\n                // in the process of caching it, and this work should not be duplicated.\n                if (this.inFlightRequests.has(req.url)) {\n                    // There is a caching operation already in progress for this request. Wait for it to\n                    // complete, and hopefully it will have yielded a useful response.\n                    return this.inFlightRequests.get(req.url);\n                }\n                // No other caching operation is being attempted for this resource, so it will be owned here.\n                // Go to the network and get the correct version.\n                const fetchOp = this.fetchFromNetwork(req);\n                // Save this operation in `inFlightRequests` so any other \"thread\" attempting to cache it\n                // will block on this chain instead of duplicating effort.\n                this.inFlightRequests.set(req.url, fetchOp);\n                // Make sure this attempt is cleaned up properly on failure.\n                try {\n                    // Wait for a response. If this fails, the request will remain in `inFlightRequests`\n                    // indefinitely.\n                    const res = yield fetchOp;\n                    // It's very important that only successful responses are cached. Unsuccessful responses\n                    // should never be cached as this can completely break applications.\n                    if (!res.ok) {\n                        throw new Error(`Response not Ok (fetchAndCacheOnce): request for ${req.url} returned response ${res.status} ${res.statusText}`);\n                    }\n                    try {\n                        // This response is safe to cache (as long as it's cloned). Wait until the cache operation\n                        // is complete.\n                        const cache = yield this.scope.caches.open(`${this.prefix}:${this.config.name}:cache`);\n                        yield cache.put(req, res.clone());\n                        // If the request is not hashed, update its metadata, especially the timestamp. This is\n                        // needed for future determination of whether this cached response is stale or not.\n                        if (!this.hashes.has(req.url)) {\n                            // Metadata is tracked for requests that are unhashed.\n                            const meta = { ts: this.adapter.time, used };\n                            const metaTable = yield this.metadata;\n                            yield metaTable.write(req.url, meta);\n                        }\n                        return res;\n                    }\n                    catch (err) {\n                        // Among other cases, this can happen when the user clears all data through the DevTools,\n                        // but the SW is still running and serving another tab. In that case, trying to write to the\n                        // caches throws an `Entry was not found` error.\n                        // If this happens the SW can no longer work correctly. This situation is unrecoverable.\n                        throw new SwCriticalError(`Failed to update the caches for request to '${req.url}' (fetchAndCacheOnce): ${errorToString(err)}`);\n                    }\n                }\n                finally {\n                    // Finally, it can be removed from `inFlightRequests`. This might result in a double-remove\n                    // if some other chain was already making this request too, but that won't hurt anything.\n                    this.inFlightRequests.delete(req.url);\n                }\n            });\n        }\n        fetchFromNetwork(req, redirectLimit = 3) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Make a cache-busted request for the resource.\n                const res = yield this.cacheBustedFetchFromNetwork(req);\n                // Check for redirected responses, and follow the redirects.\n                if (res['redirected'] && !!res.url) {\n                    // If the redirect limit is exhausted, fail with an error.\n                    if (redirectLimit === 0) {\n                        throw new SwCriticalError(`Response hit redirect limit (fetchFromNetwork): request redirected too many times, next is ${res.url}`);\n                    }\n                    // Unwrap the redirect directly.\n                    return this.fetchFromNetwork(this.adapter.newRequest(res.url), redirectLimit - 1);\n                }\n                return res;\n            });\n        }\n        /**\n         * Load a particular asset from the network, accounting for hash validation.\n         */\n        cacheBustedFetchFromNetwork(req) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                // If a hash is available for this resource, then compare the fetched version with the\n                // canonical hash. Otherwise, the network version will have to be trusted.\n                if (this.hashes.has(url)) {\n                    // It turns out this resource does have a hash. Look it up. Unless the fetched version\n                    // matches this hash, it's invalid and the whole manifest may need to be thrown out.\n                    const canonicalHash = this.hashes.get(url);\n                    // Ideally, the resource would be requested with cache-busting to guarantee the SW gets\n                    // the freshest version. However, doing this would eliminate any chance of the response\n                    // being in the HTTP cache. Given that the browser has recently actively loaded the page,\n                    // it's likely that many of the responses the SW needs to cache are in the HTTP cache and\n                    // are fresh enough to use. In the future, this could be done by setting cacheMode to\n                    // *only* check the browser cache for a cached version of the resource, when cacheMode is\n                    // fully supported. For now, the resource is fetched directly, without cache-busting, and\n                    // if the hash test fails a cache-busted request is tried before concluding that the\n                    // resource isn't correct. This gives the benefit of acceleration via the HTTP cache\n                    // without the risk of stale data, at the expense of a duplicate request in the event of\n                    // a stale response.\n                    // Fetch the resource from the network (possibly hitting the HTTP cache).\n                    const networkResult = yield this.safeFetch(req);\n                    // Decide whether a cache-busted request is necessary. It might be for two independent\n                    // reasons: either the non-cache-busted request failed (hopefully transiently) or if the\n                    // hash of the content retrieved does not match the canonical hash from the manifest. It's\n                    // only valid to access the content of the first response if the request was successful.\n                    let makeCacheBustedRequest = networkResult.ok;\n                    if (makeCacheBustedRequest) {\n                        // The request was successful. A cache-busted request is only necessary if the hashes\n                        // don't match. Compare them, making sure to clone the response so it can be used later\n                        // if it proves to be valid.\n                        const fetchedHash = sha1Binary(yield networkResult.clone().arrayBuffer());\n                        makeCacheBustedRequest = (fetchedHash !== canonicalHash);\n                    }\n                    // Make a cache busted request to the network, if necessary.\n                    if (makeCacheBustedRequest) {\n                        // Hash failure, the version that was retrieved under the default URL did not have the\n                        // hash expected. This could be because the HTTP cache got in the way and returned stale\n                        // data, or because the version on the server really doesn't match. A cache-busting\n                        // request will differentiate these two situations.\n                        // TODO: handle case where the URL has parameters already (unlikely for assets).\n                        const cacheBustReq = this.adapter.newRequest(this.cacheBust(req.url));\n                        const cacheBustedResult = yield this.safeFetch(cacheBustReq);\n                        // If the response was unsuccessful, there's nothing more that can be done.\n                        if (!cacheBustedResult.ok) {\n                            throw new SwCriticalError(`Response not Ok (cacheBustedFetchFromNetwork): cache busted request for ${req.url} returned response ${cacheBustedResult.status} ${cacheBustedResult.statusText}`);\n                        }\n                        // Hash the contents.\n                        const cacheBustedHash = sha1Binary(yield cacheBustedResult.clone().arrayBuffer());\n                        // If the cache-busted version doesn't match, then the manifest is not an accurate\n                        // representation of the server's current set of files, and the SW should give up.\n                        if (canonicalHash !== cacheBustedHash) {\n                            throw new SwCriticalError(`Hash mismatch (cacheBustedFetchFromNetwork): ${req.url}: expected ${canonicalHash}, got ${cacheBustedHash} (after cache busting)`);\n                        }\n                        // If it does match, then use the cache-busted result.\n                        return cacheBustedResult;\n                    }\n                    // Excellent, the version from the network matched on the first try, with no need for\n                    // cache-busting. Use it.\n                    return networkResult;\n                }\n                else {\n                    // This URL doesn't exist in our hash database, so it must be requested directly.\n                    return this.safeFetch(req);\n                }\n            });\n        }\n        /**\n         * Possibly update a resource, if it's expired and needs to be updated. A no-op otherwise.\n         */\n        maybeUpdate(updateFrom, req, cache) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                const meta = yield this.metadata;\n                // Check if this resource is hashed and already exists in the cache of a prior version.\n                if (this.hashes.has(url)) {\n                    const hash = this.hashes.get(url);\n                    // Check the caches of prior versions, using the hash to ensure the correct version of\n                    // the resource is loaded.\n                    const res = yield updateFrom.lookupResourceWithHash(url, hash);\n                    // If a previously cached version was available, copy it over to this cache.\n                    if (res !== null) {\n                        // Copy to this cache.\n                        yield cache.put(req, res);\n                        yield meta.write(req.url, { ts: this.adapter.time, used: false });\n                        // No need to do anything further with this resource, it's now cached properly.\n                        return true;\n                    }\n                }\n                // No up-to-date version of this resource could be found.\n                return false;\n            });\n        }\n        /**\n         * Construct a cache-busting URL for a given URL.\n         */\n        cacheBust(url) {\n            return url + (url.indexOf('?') === -1 ? '?' : '&') + 'ngsw-cache-bust=' + Math.random();\n        }\n        safeFetch(req) {\n            return __awaiter(this, void 0, void 0, function* () {\n                try {\n                    return yield this.scope.fetch(req);\n                }\n                catch (_a) {\n                    return this.adapter.newResponse('', {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n    /**\n     * An `AssetGroup` that prefetches all of its resources during initialization.\n     */\n    class PrefetchAssetGroup extends AssetGroup {\n        initializeFully(updateFrom) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Open the cache which actually holds requests.\n                const cache = yield this.cache;\n                // Cache all known resources serially. As this reduce proceeds, each Promise waits\n                // on the last before starting the fetch/cache operation for the next request. Any\n                // errors cause fall-through to the final Promise which rejects.\n                yield this.config.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                    // Wait on all previous operations to complete.\n                    yield previous;\n                    // Construct the Request for this url.\n                    const req = this.adapter.newRequest(url);\n                    // First, check the cache to see if there is already a copy of this resource.\n                    const alreadyCached = (yield cache.match(req)) !== undefined;\n                    // If the resource is in the cache already, it can be skipped.\n                    if (alreadyCached) {\n                        return;\n                    }\n                    // If an update source is available.\n                    if (updateFrom !== undefined && (yield this.maybeUpdate(updateFrom, req, cache))) {\n                        return;\n                    }\n                    // Otherwise, go to the network and hopefully cache the response (if successful).\n                    yield this.fetchAndCacheOnce(req, false);\n                }), Promise.resolve());\n                // Handle updating of unknown (unhashed) resources. This is only possible if there's\n                // a source to update from.\n                if (updateFrom !== undefined) {\n                    const metaTable = yield this.metadata;\n                    // Select all of the previously cached resources. These are cached unhashed resources\n                    // from previous versions of the app, in any asset group.\n                    yield (yield updateFrom.previouslyCachedResources())\n                        // First, narrow down the set of resources to those which are handled by this group.\n                        // Either it's a known URL, or it matches a given pattern.\n                        .filter(url => this.config.urls.some(cacheUrl => cacheUrl === url) ||\n                        this.patterns.some(pattern => pattern.test(url)))\n                        // Finally, process each resource in turn.\n                        .reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                        yield previous;\n                        const req = this.adapter.newRequest(url);\n                        // It's possible that the resource in question is already cached. If so,\n                        // continue to the next one.\n                        const alreadyCached = ((yield cache.match(req)) !== undefined);\n                        if (alreadyCached) {\n                            return;\n                        }\n                        // Get the most recent old version of the resource.\n                        const res = yield updateFrom.lookupResourceWithoutHash(url);\n                        if (res === null || res.metadata === undefined) {\n                            // Unexpected, but not harmful.\n                            return;\n                        }\n                        // Write it into the cache. It may already be expired, but it can still serve\n                        // traffic until it's updated (stale-while-revalidate approach).\n                        yield cache.put(req, res.response);\n                        yield metaTable.write(url, Object.assign(Object.assign({}, res.metadata), { used: false }));\n                    }), Promise.resolve());\n                }\n            });\n        }\n    }\n    class LazyAssetGroup extends AssetGroup {\n        initializeFully(updateFrom) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // No action necessary if no update source is available - resources managed in this group\n                // are all lazily loaded, so there's nothing to initialize.\n                if (updateFrom === undefined) {\n                    return;\n                }\n                // Open the cache which actually holds requests.\n                const cache = yield this.cache;\n                // Loop through the listed resources, caching any which are available.\n                yield this.config.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                    // Wait on all previous operations to complete.\n                    yield previous;\n                    // Construct the Request for this url.\n                    const req = this.adapter.newRequest(url);\n                    // First, check the cache to see if there is already a copy of this resource.\n                    const alreadyCached = (yield cache.match(req)) !== undefined;\n                    // If the resource is in the cache already, it can be skipped.\n                    if (alreadyCached) {\n                        return;\n                    }\n                    const updated = yield this.maybeUpdate(updateFrom, req, cache);\n                    if (this.config.updateMode === 'prefetch' && !updated) {\n                        // If the resource was not updated, either it was not cached before or\n                        // the previously cached version didn't match the updated hash. In that\n                        // case, prefetch update mode dictates that the resource will be updated,\n                        // except if it was not previously utilized. Check the status of the\n                        // cached resource to see.\n                        const cacheStatus = yield updateFrom.recentCacheStatus(url);\n                        // If the resource is not cached, or was cached but unused, then it will be\n                        // loaded lazily.\n                        if (cacheStatus !== UpdateCacheStatus.CACHED) {\n                            return;\n                        }\n                        // Update from the network.\n                        yield this.fetchAndCacheOnce(req, false);\n                    }\n                }), Promise.resolve());\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    /**\n     * Manages an instance of `LruState` and moves URLs to the head of the\n     * chain when requested.\n     */\n    class LruList {\n        constructor(state) {\n            if (state === undefined) {\n                state = {\n                    head: null,\n                    tail: null,\n                    map: {},\n                    count: 0,\n                };\n            }\n            this.state = state;\n        }\n        /**\n         * The current count of URLs in the list.\n         */\n        get size() { return this.state.count; }\n        /**\n         * Remove the tail.\n         */\n        pop() {\n            // If there is no tail, return null.\n            if (this.state.tail === null) {\n                return null;\n            }\n            const url = this.state.tail;\n            this.remove(url);\n            // This URL has been successfully evicted.\n            return url;\n        }\n        remove(url) {\n            const node = this.state.map[url];\n            if (node === undefined) {\n                return false;\n            }\n            // Special case if removing the current head.\n            if (this.state.head === url) {\n                // The node is the current head. Special case the removal.\n                if (node.next === null) {\n                    // This is the only node. Reset the cache to be empty.\n                    this.state.head = null;\n                    this.state.tail = null;\n                    this.state.map = {};\n                    this.state.count = 0;\n                    return true;\n                }\n                // There is at least one other node. Make the next node the new head.\n                const next = this.state.map[node.next];\n                next.previous = null;\n                this.state.head = next.url;\n                node.next = null;\n                delete this.state.map[url];\n                this.state.count--;\n                return true;\n            }\n            // The node is not the head, so it has a previous. It may or may not be the tail.\n            // If it is not, then it has a next. First, grab the previous node.\n            const previous = this.state.map[node.previous];\n            // Fix the forward pointer to skip over node and go directly to node.next.\n            previous.next = node.next;\n            // node.next may or may not be set. If it is, fix the back pointer to skip over node.\n            // If it's not set, then this node happened to be the tail, and the tail needs to be\n            // updated to point to the previous node (removing the tail).\n            if (node.next !== null) {\n                // There is a next node, fix its back pointer to skip this node.\n                this.state.map[node.next].previous = node.previous;\n            }\n            else {\n                // There is no next node - the accessed node must be the tail. Move the tail pointer.\n                this.state.tail = node.previous;\n            }\n            node.next = null;\n            node.previous = null;\n            delete this.state.map[url];\n            // Count the removal.\n            this.state.count--;\n            return true;\n        }\n        accessed(url) {\n            // When a URL is accessed, its node needs to be moved to the head of the chain.\n            // This is accomplished in two steps:\n            //\n            // 1) remove the node from its position within the chain.\n            // 2) insert the node as the new head.\n            //\n            // Sometimes, a URL is accessed which has not been seen before. In this case, step 1 can\n            // be skipped completely (which will grow the chain by one). Of course, if the node is\n            // already the head, this whole operation can be skipped.\n            if (this.state.head === url) {\n                // The URL is already in the head position, accessing it is a no-op.\n                return;\n            }\n            // Look up the node in the map, and construct a new entry if it's\n            const node = this.state.map[url] || { url, next: null, previous: null };\n            // Step 1: remove the node from its position within the chain, if it is in the chain.\n            if (this.state.map[url] !== undefined) {\n                this.remove(url);\n            }\n            // Step 2: insert the node at the head of the chain.\n            // First, check if there's an existing head node. If there is, it has previous: null.\n            // Its previous pointer should be set to the node we're inserting.\n            if (this.state.head !== null) {\n                this.state.map[this.state.head].previous = url;\n            }\n            // The next pointer of the node being inserted gets set to the old head, before the head\n            // pointer is updated to this node.\n            node.next = this.state.head;\n            // The new head is the new node.\n            this.state.head = url;\n            // If there is no tail, then this is the first node, and is both the head and the tail.\n            if (this.state.tail === null) {\n                this.state.tail = url;\n            }\n            // Set the node in the map of nodes (if the URL has been seen before, this is a no-op)\n            // and count the insertion.\n            this.state.map[url] = node;\n            this.state.count++;\n        }\n    }\n    /**\n     * A group of cached resources determined by a set of URL patterns which follow a LRU policy\n     * for caching.\n     */\n    class DataGroup {\n        constructor(scope, adapter, config, db, debugHandler, prefix) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.config = config;\n            this.db = db;\n            this.debugHandler = debugHandler;\n            this.prefix = prefix;\n            /**\n             * Tracks the LRU state of resources in this cache.\n             */\n            this._lru = null;\n            this.patterns = this.config.patterns.map(pattern => new RegExp(pattern));\n            this.cache = this.scope.caches.open(`${this.prefix}:dynamic:${this.config.name}:cache`);\n            this.lruTable = this.db.open(`${this.prefix}:dynamic:${this.config.name}:lru`);\n            this.ageTable = this.db.open(`${this.prefix}:dynamic:${this.config.name}:age`);\n        }\n        /**\n         * Lazily initialize/load the LRU chain.\n         */\n        lru() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                if (this._lru === null) {\n                    const table = yield this.lruTable;\n                    try {\n                        this._lru = new LruList(yield table.read('lru'));\n                    }\n                    catch (_a) {\n                        this._lru = new LruList();\n                    }\n                }\n                return this._lru;\n            });\n        }\n        /**\n         * Sync the LRU chain to non-volatile storage.\n         */\n        syncLru() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                if (this._lru === null) {\n                    return;\n                }\n                const table = yield this.lruTable;\n                try {\n                    return table.write('lru', this._lru.state);\n                }\n                catch (err) {\n                    // Writing lru cache table failed. This could be a result of a full storage.\n                    // Continue serving clients as usual.\n                    this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).syncLru()`);\n                    // TODO: Better detect/handle full storage; e.g. using\n                    // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).\n                }\n            });\n        }\n        /**\n         * Process a fetch event and return a `Response` if the resource is covered by this group,\n         * or `null` otherwise.\n         */\n        handleFetch(req, ctx) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Do nothing\n                if (!this.patterns.some(pattern => pattern.test(req.url))) {\n                    return null;\n                }\n                // Lazily initialize the LRU cache.\n                const lru = yield this.lru();\n                // The URL matches this cache. First, check whether this is a mutating request or not.\n                switch (req.method) {\n                    case 'OPTIONS':\n                        // Don't try to cache this - it's non-mutating, but is part of a mutating request.\n                        // Most likely SWs don't even see this, but this guard is here just in case.\n                        return null;\n                    case 'GET':\n                    case 'HEAD':\n                        // Handle the request with whatever strategy was selected.\n                        switch (this.config.strategy) {\n                            case 'freshness':\n                                return this.handleFetchWithFreshness(req, ctx, lru);\n                            case 'performance':\n                                return this.handleFetchWithPerformance(req, ctx, lru);\n                            default:\n                                throw new Error(`Unknown strategy: ${this.config.strategy}`);\n                        }\n                    default:\n                        // This was a mutating request. Assume the cache for this URL is no longer valid.\n                        const wasCached = lru.remove(req.url);\n                        // If there was a cached entry, remove it.\n                        if (wasCached) {\n                            yield this.clearCacheForUrl(req.url);\n                        }\n                        // Sync the LRU chain to non-volatile storage.\n                        yield this.syncLru();\n                        // Finally, fall back on the network.\n                        return this.safeFetch(req);\n                }\n            });\n        }\n        handleFetchWithPerformance(req, ctx, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                let res = null;\n                // Check the cache first. If the resource exists there (and is not expired), the cached\n                // version can be used.\n                const fromCache = yield this.loadFromCache(req, lru);\n                if (fromCache !== null) {\n                    res = fromCache.res;\n                    // Check the age of the resource.\n                    if (this.config.refreshAheadMs !== undefined && fromCache.age >= this.config.refreshAheadMs) {\n                        ctx.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru));\n                    }\n                }\n                if (res !== null) {\n                    return res;\n                }\n                // No match from the cache. Go to the network. Note that this is not an 'await'\n                // call, networkFetch is the actual Promise. This is due to timeout handling.\n                const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);\n                res = yield timeoutFetch;\n                // Since fetch() will always return a response, undefined indicates a timeout.\n                if (res === undefined) {\n                    // The request timed out. Return a Gateway Timeout error.\n                    res = this.adapter.newResponse(null, { status: 504, statusText: 'Gateway Timeout' });\n                    // Cache the network response eventually.\n                    ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru));\n                }\n                else {\n                    // The request completed in time, so cache it inline with the response flow.\n                    yield this.safeCacheResponse(req, res, lru);\n                }\n                return res;\n            });\n        }\n        handleFetchWithFreshness(req, ctx, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Start with a network fetch.\n                const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);\n                let res;\n                // If that fetch errors, treat it as a timed out request.\n                try {\n                    res = yield timeoutFetch;\n                }\n                catch (_a) {\n                    res = undefined;\n                }\n                // If the network fetch times out or errors, fall back on the cache.\n                if (res === undefined) {\n                    ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true));\n                    // Ignore the age, the network response will be cached anyway due to the\n                    // behavior of freshness.\n                    const fromCache = yield this.loadFromCache(req, lru);\n                    res = (fromCache !== null) ? fromCache.res : null;\n                }\n                else {\n                    yield this.safeCacheResponse(req, res, lru, true);\n                }\n                // Either the network fetch didn't time out, or the cache yielded a usable response.\n                // In either case, use it.\n                if (res !== null) {\n                    return res;\n                }\n                // No response in the cache. No choice but to fall back on the full network fetch.\n                return networkFetch;\n            });\n        }\n        networkFetchWithTimeout(req) {\n            // If there is a timeout configured, race a timeout Promise with the network fetch.\n            // Otherwise, just fetch from the network directly.\n            if (this.config.timeoutMs !== undefined) {\n                const networkFetch = this.scope.fetch(req);\n                const safeNetworkFetch = (() => __awaiter$1(this, void 0, void 0, function* () {\n                    try {\n                        return yield networkFetch;\n                    }\n                    catch (_a) {\n                        return this.adapter.newResponse(null, {\n                            status: 504,\n                            statusText: 'Gateway Timeout',\n                        });\n                    }\n                }))();\n                const networkFetchUndefinedError = (() => __awaiter$1(this, void 0, void 0, function* () {\n                    try {\n                        return yield networkFetch;\n                    }\n                    catch (_b) {\n                        return undefined;\n                    }\n                }))();\n                // Construct a Promise<undefined> for the timeout.\n                const timeout = this.adapter.timeout(this.config.timeoutMs);\n                // Race that with the network fetch. This will either be a Response, or `undefined`\n                // in the event that the request errored or timed out.\n                return [Promise.race([networkFetchUndefinedError, timeout]), safeNetworkFetch];\n            }\n            else {\n                const networkFetch = this.safeFetch(req);\n                // Do a plain fetch.\n                return [networkFetch, networkFetch];\n            }\n        }\n        safeCacheResponse(req, resOrPromise, lru, okToCacheOpaque) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                try {\n                    const res = yield resOrPromise;\n                    try {\n                        yield this.cacheResponse(req, res, lru, okToCacheOpaque);\n                    }\n                    catch (err) {\n                        // Saving the API response failed. This could be a result of a full storage.\n                        // Since this data is cached lazily and temporarily, continue serving clients as usual.\n                        this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).safeCacheResponse(${req.url}, status: ${res.status})`);\n                        // TODO: Better detect/handle full storage; e.g. using\n                        // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).\n                    }\n                }\n                catch (_a) {\n                    // Request failed\n                    // TODO: Handle this error somehow?\n                }\n            });\n        }\n        loadFromCache(req, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Look for a response in the cache. If one exists, return it.\n                const cache = yield this.cache;\n                let res = yield cache.match(req);\n                if (res !== undefined) {\n                    // A response was found in the cache, but its age is not yet known. Look it up.\n                    try {\n                        const ageTable = yield this.ageTable;\n                        const age = this.adapter.time - (yield ageTable.read(req.url)).age;\n                        // If the response is young enough, use it.\n                        if (age <= this.config.maxAge) {\n                            // Successful match from the cache. Use the response, after marking it as having\n                            // been accessed.\n                            lru.accessed(req.url);\n                            return { res, age };\n                        }\n                        // Otherwise, or if there was an error, assume the response is expired, and evict it.\n                    }\n                    catch (_a) {\n                        // Some error getting the age for the response. Assume it's expired.\n                    }\n                    lru.remove(req.url);\n                    yield this.clearCacheForUrl(req.url);\n                    // TODO: avoid duplicate in event of network timeout, maybe.\n                    yield this.syncLru();\n                }\n                return null;\n            });\n        }\n        /**\n         * Operation for caching the response from the server. This has to happen all\n         * at once, so that the cache and LRU tracking remain in sync. If the network request\n         * completes before the timeout, this logic will be run inline with the response flow.\n         * If the request times out on the server, an error will be returned but the real network\n         * request will still be running in the background, to be cached when it completes.\n         */\n        cacheResponse(req, res, lru, okToCacheOpaque = false) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Only cache successful responses.\n                if (!(res.ok || (okToCacheOpaque && res.type === 'opaque'))) {\n                    return;\n                }\n                // If caching this response would make the cache exceed its maximum size, evict something\n                // first.\n                if (lru.size >= this.config.maxSize) {\n                    // The cache is too big, evict something.\n                    const evictedUrl = lru.pop();\n                    if (evictedUrl !== null) {\n                        yield this.clearCacheForUrl(evictedUrl);\n                    }\n                }\n                // TODO: evaluate for possible race conditions during flaky network periods.\n                // Mark this resource as having been accessed recently. This ensures it won't be evicted\n                // until enough other resources are requested that it falls off the end of the LRU chain.\n                lru.accessed(req.url);\n                // Store the response in the cache (cloning because the browser will consume\n                // the body during the caching operation).\n                yield (yield this.cache).put(req, res.clone());\n                // Store the age of the cache.\n                const ageTable = yield this.ageTable;\n                yield ageTable.write(req.url, { age: this.adapter.time });\n                // Sync the LRU chain to non-volatile storage.\n                yield this.syncLru();\n            });\n        }\n        /**\n         * Delete all of the saved state which this group uses to track resources.\n         */\n        cleanup() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Remove both the cache and the database entries which track LRU stats.\n                yield Promise.all([\n                    this.scope.caches.delete(`${this.prefix}:dynamic:${this.config.name}:cache`),\n                    this.db.delete(`${this.prefix}:dynamic:${this.config.name}:age`),\n                    this.db.delete(`${this.prefix}:dynamic:${this.config.name}:lru`),\n                ]);\n            });\n        }\n        /**\n         * Clear the state of the cache for a particular resource.\n         *\n         * This doesn't remove the resource from the LRU table, that is assumed to have\n         * been done already. This clears the GET and HEAD versions of the request from\n         * the cache itself, as well as the metadata stored in the age table.\n         */\n        clearCacheForUrl(url) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                const [cache, ageTable] = yield Promise.all([this.cache, this.ageTable]);\n                yield Promise.all([\n                    cache.delete(this.adapter.newRequest(url, { method: 'GET' })),\n                    cache.delete(this.adapter.newRequest(url, { method: 'HEAD' })),\n                    ageTable.delete(url),\n                ]);\n            });\n        }\n        safeFetch(req) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                try {\n                    return this.scope.fetch(req);\n                }\n                catch (_a) {\n                    return this.adapter.newResponse(null, {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const BACKWARDS_COMPATIBILITY_NAVIGATION_URLS = [\n        { positive: true, regex: '^/.*$' },\n        { positive: false, regex: '^/.*\\\\.[^/]*$' },\n        { positive: false, regex: '^/.*__' },\n    ];\n    /**\n     * A specific version of the application, identified by a unique manifest\n     * as determined by its hash.\n     *\n     * Each `AppVersion` can be thought of as a published version of the app\n     * that can be installed as an update to any previously installed versions.\n     */\n    class AppVersion {\n        constructor(scope, adapter, database, idle, debugHandler, manifest, manifestHash) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.database = database;\n            this.idle = idle;\n            this.debugHandler = debugHandler;\n            this.manifest = manifest;\n            this.manifestHash = manifestHash;\n            /**\n             * A Map of absolute URL paths (/foo.txt) to the known hash of their\n             * contents (if available).\n             */\n            this.hashTable = new Map();\n            /**\n             * Tracks whether the manifest has encountered any inconsistencies.\n             */\n            this._okay = true;\n            // The hashTable within the manifest is an Object - convert it to a Map for easier lookups.\n            Object.keys(this.manifest.hashTable).forEach(url => {\n                this.hashTable.set(url, this.manifest.hashTable[url]);\n            });\n            // Process each `AssetGroup` declared in the manifest. Each declared group gets an `AssetGroup`\n            // instance\n            // created for it, of a type that depends on the configuration mode.\n            this.assetGroups = (manifest.assetGroups || []).map(config => {\n                // Every asset group has a cache that's prefixed by the manifest hash and the name of the\n                // group.\n                const prefix = `${adapter.cacheNamePrefix}:${this.manifestHash}:assets`;\n                // Check the caching mode, which determines when resources will be fetched/updated.\n                switch (config.installMode) {\n                    case 'prefetch':\n                        return new PrefetchAssetGroup(this.scope, this.adapter, this.idle, config, this.hashTable, this.database, prefix);\n                    case 'lazy':\n                        return new LazyAssetGroup(this.scope, this.adapter, this.idle, config, this.hashTable, this.database, prefix);\n                }\n            });\n            // Process each `DataGroup` declared in the manifest.\n            this.dataGroups =\n                (manifest.dataGroups || [])\n                    .map(config => new DataGroup(this.scope, this.adapter, config, this.database, this.debugHandler, `${adapter.cacheNamePrefix}:${config.version}:data`));\n            // This keeps backwards compatibility with app versions without navigation urls.\n            // Fix: https://github.com/angular/angular/issues/27209\n            manifest.navigationUrls = manifest.navigationUrls || BACKWARDS_COMPATIBILITY_NAVIGATION_URLS;\n            // Create `include`/`exclude` RegExps for the `navigationUrls` declared in the manifest.\n            const includeUrls = manifest.navigationUrls.filter(spec => spec.positive);\n            const excludeUrls = manifest.navigationUrls.filter(spec => !spec.positive);\n            this.navigationUrls = {\n                include: includeUrls.map(spec => new RegExp(spec.regex)),\n                exclude: excludeUrls.map(spec => new RegExp(spec.regex)),\n            };\n        }\n        get okay() { return this._okay; }\n        /**\n         * Fully initialize this version of the application. If this Promise resolves successfully, all\n         * required\n         * data has been safely downloaded.\n         */\n        initializeFully(updateFrom) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                try {\n                    // Fully initialize each asset group, in series. Starts with an empty Promise,\n                    // and waits for the previous groups to have been initialized before initializing\n                    // the next one in turn.\n                    yield this.assetGroups.reduce((previous, group) => __awaiter$2(this, void 0, void 0, function* () {\n                        // Wait for the previous groups to complete initialization. If there is a\n                        // failure, this will throw, and each subsequent group will throw, until the\n                        // whole sequence fails.\n                        yield previous;\n                        // Initialize this group.\n                        return group.initializeFully(updateFrom);\n                    }), Promise.resolve());\n                }\n                catch (err) {\n                    this._okay = false;\n                    throw err;\n                }\n            });\n        }\n        handleFetch(req, context) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                // Check the request against each `AssetGroup` in sequence. If an `AssetGroup` can't handle the\n                // request,\n                // it will return `null`. Thus, the first non-null response is the SW's answer to the request.\n                // So reduce\n                // the group list, keeping track of a possible response. If there is one, it gets passed\n                // through, and if\n                // not the next group is consulted to produce a candidate response.\n                const asset = yield this.assetGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    // Wait on the previous potential response. If it's not null, it should just be passed\n                    // through.\n                    const resp = yield potentialResponse;\n                    if (resp !== null) {\n                        return resp;\n                    }\n                    // No response has been found yet. Maybe this group will have one.\n                    return group.handleFetch(req, context);\n                }), Promise.resolve(null));\n                // The result of the above is the asset response, if there is any, or null otherwise. Return the\n                // asset\n                // response if there was one. If not, check with the data caching groups.\n                if (asset !== null) {\n                    return asset;\n                }\n                // Perform the same reduction operation as above, but this time processing\n                // the data caching groups.\n                const data = yield this.dataGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    const resp = yield potentialResponse;\n                    if (resp !== null) {\n                        return resp;\n                    }\n                    return group.handleFetch(req, context);\n                }), Promise.resolve(null));\n                // If the data caching group returned a response, go with it.\n                if (data !== null) {\n                    return data;\n                }\n                // Next, check if this is a navigation request for a route. Detect circular\n                // navigations by checking if the request URL is the same as the index URL.\n                if (req.url !== this.manifest.index && this.isNavigationRequest(req)) {\n                    // This was a navigation request. Re-enter `handleFetch` with a request for\n                    // the URL.\n                    return this.handleFetch(this.adapter.newRequest(this.manifest.index), context);\n                }\n                return null;\n            });\n        }\n        /**\n         * Determine whether the request is a navigation request.\n         * Takes into account: Request mode, `Accept` header, `navigationUrls` patterns.\n         */\n        isNavigationRequest(req) {\n            if (req.mode !== 'navigate') {\n                return false;\n            }\n            if (!this.acceptsTextHtml(req)) {\n                return false;\n            }\n            const urlPrefix = this.scope.registration.scope.replace(/\\/$/, '');\n            const url = req.url.startsWith(urlPrefix) ? req.url.substr(urlPrefix.length) : req.url;\n            const urlWithoutQueryOrHash = url.replace(/[?#].*$/, '');\n            return this.navigationUrls.include.some(regex => regex.test(urlWithoutQueryOrHash)) &&\n                !this.navigationUrls.exclude.some(regex => regex.test(urlWithoutQueryOrHash));\n        }\n        /**\n         * Check this version for a given resource with a particular hash.\n         */\n        lookupResourceWithHash(url, hash) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                // Verify that this version has the requested resource cached. If not,\n                // there's no point in trying.\n                if (!this.hashTable.has(url)) {\n                    return null;\n                }\n                // Next, check whether the resource has the correct hash. If not, any cached\n                // response isn't usable.\n                if (this.hashTable.get(url) !== hash) {\n                    return null;\n                }\n                const cacheState = yield this.lookupResourceWithoutHash(url);\n                return cacheState && cacheState.response;\n            });\n        }\n        /**\n         * Check this version for a given resource regardless of its hash.\n         */\n        lookupResourceWithoutHash(url) {\n            // Limit the search to asset groups, and only scan the cache, don't\n            // load resources from the network.\n            return this.assetGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                const resp = yield potentialResponse;\n                if (resp !== null) {\n                    return resp;\n                }\n                // fetchFromCacheOnly() avoids any network fetches, and returns the\n                // full set of cache data, not just the Response.\n                return group.fetchFromCacheOnly(url);\n            }), Promise.resolve(null));\n        }\n        /**\n         * List all unhashed resources from all asset groups.\n         */\n        previouslyCachedResources() {\n            return this.assetGroups.reduce((resources, group) => __awaiter$2(this, void 0, void 0, function* () {\n                return (yield resources).concat(yield group.unhashedResources());\n            }), Promise.resolve([]));\n        }\n        recentCacheStatus(url) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                return this.assetGroups.reduce((current, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    const status = yield current;\n                    if (status === UpdateCacheStatus.CACHED) {\n                        return status;\n                    }\n                    const groupStatus = yield group.cacheStatus(url);\n                    if (groupStatus === UpdateCacheStatus.NOT_CACHED) {\n                        return status;\n                    }\n                    return groupStatus;\n                }), Promise.resolve(UpdateCacheStatus.NOT_CACHED));\n            });\n        }\n        /**\n         * Erase this application version, by cleaning up all the caches.\n         */\n        cleanup() {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                yield Promise.all(this.assetGroups.map(group => group.cleanup()));\n                yield Promise.all(this.dataGroups.map(group => group.cleanup()));\n            });\n        }\n        /**\n         * Get the opaque application data which was provided with the manifest.\n         */\n        get appData() { return this.manifest.appData || null; }\n        /**\n         * Check whether a request accepts `text/html` (based on the `Accept` header).\n         */\n        acceptsTextHtml(req) {\n            const accept = req.headers.get('Accept');\n            if (accept === null) {\n                return false;\n            }\n            const values = accept.split(',');\n            return values.some(value => value.trim().toLowerCase() === 'text/html');\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$3 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const DEBUG_LOG_BUFFER_SIZE = 100;\n    class DebugHandler {\n        constructor(driver, adapter) {\n            this.driver = driver;\n            this.adapter = adapter;\n            // There are two debug log message arrays. debugLogA records new debugging messages.\n            // Once it reaches DEBUG_LOG_BUFFER_SIZE, the array is moved to debugLogB and a new\n            // array is assigned to debugLogA. This ensures that insertion to the debug log is\n            // always O(1) no matter the number of logged messages, and that the total number\n            // of messages in the log never exceeds 2 * DEBUG_LOG_BUFFER_SIZE.\n            this.debugLogA = [];\n            this.debugLogB = [];\n        }\n        handleFetch(req) {\n            return __awaiter$3(this, void 0, void 0, function* () {\n                const [state, versions, idle] = yield Promise.all([\n                    this.driver.debugState(),\n                    this.driver.debugVersions(),\n                    this.driver.debugIdleState(),\n                ]);\n                const msgState = `NGSW Debug Info:\n\nDriver state: ${state.state} (${state.why})\nLatest manifest hash: ${state.latestHash || 'none'}\nLast update check: ${this.since(state.lastUpdateCheck)}`;\n                const msgVersions = versions\n                    .map(version => `=== Version ${version.hash} ===\n\nClients: ${version.clients.join(', ')}`)\n                    .join('\\n\\n');\n                const msgIdle = `=== Idle Task Queue ===\nLast update tick: ${this.since(idle.lastTrigger)}\nLast update run: ${this.since(idle.lastRun)}\nTask queue:\n${idle.queue.map(v => ' * ' + v).join('\\n')}\n\nDebug log:\n${this.formatDebugLog(this.debugLogB)}\n${this.formatDebugLog(this.debugLogA)}\n`;\n                return this.adapter.newResponse(`${msgState}\n\n${msgVersions}\n\n${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }) });\n            });\n        }\n        since(time) {\n            if (time === null) {\n                return 'never';\n            }\n            let age = this.adapter.time - time;\n            const days = Math.floor(age / 86400000);\n            age = age % 86400000;\n            const hours = Math.floor(age / 3600000);\n            age = age % 3600000;\n            const minutes = Math.floor(age / 60000);\n            age = age % 60000;\n            const seconds = Math.floor(age / 1000);\n            const millis = age % 1000;\n            return '' + (days > 0 ? `${days}d` : '') + (hours > 0 ? `${hours}h` : '') +\n                (minutes > 0 ? `${minutes}m` : '') + (seconds > 0 ? `${seconds}s` : '') +\n                (millis > 0 ? `${millis}u` : '');\n        }\n        log(value, context = '') {\n            // Rotate the buffers if debugLogA has grown too large.\n            if (this.debugLogA.length === DEBUG_LOG_BUFFER_SIZE) {\n                this.debugLogB = this.debugLogA;\n                this.debugLogA = [];\n            }\n            // Convert errors to string for logging.\n            if (typeof value !== 'string') {\n                value = this.errorToString(value);\n            }\n            // Log the message.\n            this.debugLogA.push({ value, time: this.adapter.time, context });\n        }\n        errorToString(err) { return `${err.name}(${err.message}, ${err.stack})`; }\n        formatDebugLog(log) {\n            return log.map(entry => `[${this.since(entry.time)}] ${entry.value} ${entry.context}`)\n                .join('\\n');\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$4 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    class IdleScheduler {\n        constructor(adapter, threshold, debug) {\n            this.adapter = adapter;\n            this.threshold = threshold;\n            this.debug = debug;\n            this.queue = [];\n            this.scheduled = null;\n            this.empty = Promise.resolve();\n            this.emptyResolve = null;\n            this.lastTrigger = null;\n            this.lastRun = null;\n        }\n        trigger() {\n            return __awaiter$4(this, void 0, void 0, function* () {\n                this.lastTrigger = this.adapter.time;\n                if (this.queue.length === 0) {\n                    return;\n                }\n                if (this.scheduled !== null) {\n                    this.scheduled.cancel = true;\n                }\n                const scheduled = {\n                    cancel: false,\n                };\n                this.scheduled = scheduled;\n                yield this.adapter.timeout(this.threshold);\n                if (scheduled.cancel) {\n                    return;\n                }\n                this.scheduled = null;\n                yield this.execute();\n            });\n        }\n        execute() {\n            return __awaiter$4(this, void 0, void 0, function* () {\n                this.lastRun = this.adapter.time;\n                while (this.queue.length > 0) {\n                    const queue = this.queue;\n                    this.queue = [];\n                    yield queue.reduce((previous, task) => __awaiter$4(this, void 0, void 0, function* () {\n                        yield previous;\n                        try {\n                            yield task.run();\n                        }\n                        catch (err) {\n                            this.debug.log(err, `while running idle task ${task.desc}`);\n                        }\n                    }), Promise.resolve());\n                }\n                if (this.emptyResolve !== null) {\n                    this.emptyResolve();\n                    this.emptyResolve = null;\n                }\n                this.empty = Promise.resolve();\n            });\n        }\n        schedule(desc, run) {\n            this.queue.push({ desc, run });\n            if (this.emptyResolve === null) {\n                this.empty = new Promise(resolve => { this.emptyResolve = resolve; });\n            }\n        }\n        get size() { return this.queue.length; }\n        get taskDescriptions() { return this.queue.map(task => task.desc); }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    function hashManifest(manifest) {\n        return sha1(JSON.stringify(manifest));\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    function isMsgCheckForUpdates(msg) {\n        return msg.action === 'CHECK_FOR_UPDATES';\n    }\n    function isMsgActivateUpdate(msg) {\n        return msg.action === 'ACTIVATE_UPDATE';\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$5 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const IDLE_THRESHOLD = 5000;\n    const SUPPORTED_CONFIG_VERSION = 1;\n    const NOTIFICATION_OPTION_NAMES = [\n        'actions', 'badge', 'body', 'data', 'dir', 'icon', 'image', 'lang', 'renotify',\n        'requireInteraction', 'silent', 'tag', 'timestamp', 'title', 'vibrate'\n    ];\n    var DriverReadyState;\n    (function (DriverReadyState) {\n        // The SW is operating in a normal mode, responding to all traffic.\n        DriverReadyState[DriverReadyState[\"NORMAL\"] = 0] = \"NORMAL\";\n        // The SW does not have a clean installation of the latest version of the app, but older\n        // cached versions are safe to use so long as they don't try to fetch new dependencies.\n        // This is a degraded state.\n        DriverReadyState[DriverReadyState[\"EXISTING_CLIENTS_ONLY\"] = 1] = \"EXISTING_CLIENTS_ONLY\";\n        // The SW has decided that caching is completely unreliable, and is forgoing request\n        // handling until the next restart.\n        DriverReadyState[DriverReadyState[\"SAFE_MODE\"] = 2] = \"SAFE_MODE\";\n    })(DriverReadyState || (DriverReadyState = {}));\n    class Driver {\n        constructor(scope, adapter, db) {\n            // Set up all the event handlers that the SW needs.\n            this.scope = scope;\n            this.adapter = adapter;\n            this.db = db;\n            /**\n             * Tracks the current readiness condition under which the SW is operating. This controls\n             * whether the SW attempts to respond to some or all requests.\n             */\n            this.state = DriverReadyState.NORMAL;\n            this.stateMessage = '(nominal)';\n            /**\n             * Tracks whether the SW is in an initialized state or not. Before initialization,\n             * it's not legal to respond to requests.\n             */\n            this.initialized = null;\n            /**\n             * Maps client IDs to the manifest hash of the application version being used to serve\n             * them. If a client ID is not present here, it has not yet been assigned a version.\n             *\n             * If a ManifestHash appears here, it is also present in the `versions` map below.\n             */\n            this.clientVersionMap = new Map();\n            /**\n             * Maps manifest hashes to instances of `AppVersion` for those manifests.\n             */\n            this.versions = new Map();\n            /**\n             * The latest version fetched from the server.\n             *\n             * Valid after initialization has completed.\n             */\n            this.latestHash = null;\n            this.lastUpdateCheck = null;\n            /**\n             * Whether there is a check for updates currently scheduled due to navigation.\n             */\n            this.scheduledNavUpdateCheck = false;\n            /**\n             * Keep track of whether we have logged an invalid `only-if-cached` request.\n             * (See `.onFetch()` for details.)\n             */\n            this.loggedInvalidOnlyIfCachedRequest = false;\n            // The install event is triggered when the service worker is first installed.\n            this.scope.addEventListener('install', (event) => {\n                // SW code updates are separate from application updates, so code updates are\n                // almost as straightforward as restarting the SW. Because of this, it's always\n                // safe to skip waiting until application tabs are closed, and activate the new\n                // SW version immediately.\n                event.waitUntil(this.scope.skipWaiting());\n            });\n            // The activate event is triggered when this version of the service worker is\n            // first activated.\n            this.scope.addEventListener('activate', (event) => {\n                event.waitUntil((() => __awaiter$5(this, void 0, void 0, function* () {\n                    // As above, it's safe to take over from existing clients immediately, since the new SW\n                    // version will continue to serve the old application.\n                    yield this.scope.clients.claim();\n                    // Once all clients have been taken over, we can delete caches used by old versions of\n                    // `@angular/service-worker`, which are no longer needed. This can happen in the background.\n                    this.idle.schedule('activate: cleanup-old-sw-caches', () => __awaiter$5(this, void 0, void 0, function* () {\n                        try {\n                            yield this.cleanupOldSwCaches();\n                        }\n                        catch (err) {\n                            // Nothing to do - cleanup failed. Just log it.\n                            this.debugger.log(err, 'cleanupOldSwCaches @ activate: cleanup-old-sw-caches');\n                        }\n                    }));\n                }))());\n                // Rather than wait for the first fetch event, which may not arrive until\n                // the next time the application is loaded, the SW takes advantage of the\n                // activation event to schedule initialization. However, if this were run\n                // in the context of the 'activate' event, waitUntil() here would cause fetch\n                // events to block until initialization completed. Thus, the SW does a\n                // postMessage() to itself, to schedule a new event loop iteration with an\n                // entirely separate event context. The SW will be kept alive by waitUntil()\n                // within that separate context while initialization proceeds, while at the\n                // same time the activation event is allowed to resolve and traffic starts\n                // being served.\n                if (this.scope.registration.active !== null) {\n                    this.scope.registration.active.postMessage({ action: 'INITIALIZE' });\n                }\n            });\n            // Handle the fetch, message, and push events.\n            this.scope.addEventListener('fetch', (event) => this.onFetch(event));\n            this.scope.addEventListener('message', (event) => this.onMessage(event));\n            this.scope.addEventListener('push', (event) => this.onPush(event));\n            this.scope.addEventListener('notificationclick', (event) => this.onClick(event));\n            // The debugger generates debug pages in response to debugging requests.\n            this.debugger = new DebugHandler(this, this.adapter);\n            // The IdleScheduler will execute idle tasks after a given delay.\n            this.idle = new IdleScheduler(this.adapter, IDLE_THRESHOLD, this.debugger);\n        }\n        /**\n         * The handler for fetch events.\n         *\n         * This is the transition point between the synchronous event handler and the\n         * asynchronous execution that eventually resolves for respondWith() and waitUntil().\n         */\n        onFetch(event) {\n            const req = event.request;\n            const scopeUrl = this.scope.registration.scope;\n            const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);\n            if (req.headers.has('ngsw-bypass') || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) {\n                return;\n            }\n            // The only thing that is served unconditionally is the debug page.\n            if (requestUrlObj.path === '/ngsw/state') {\n                // Allow the debugger to handle the request, but don't affect SW state in any other way.\n                event.respondWith(this.debugger.handleFetch(req));\n                return;\n            }\n            // If the SW is in a broken state where it's not safe to handle requests at all,\n            // returning causes the request to fall back on the network. This is preferred over\n            // `respondWith(fetch(req))` because the latter still shows in DevTools that the\n            // request was handled by the SW.\n            if (this.state === DriverReadyState.SAFE_MODE) {\n                // Even though the worker is in safe mode, idle tasks still need to happen so\n                // things like update checks, etc. can take place.\n                event.waitUntil(this.idle.trigger());\n                return;\n            }\n            // Although \"passive mixed content\" (like images) only produces a warning without a\n            // ServiceWorker, fetching it via a ServiceWorker results in an error. Let such requests be\n            // handled by the browser, since handling with the ServiceWorker would fail anyway.\n            // See https://github.com/angular/angular/issues/23012#issuecomment-376430187 for more details.\n            if (requestUrlObj.origin.startsWith('http:') && scopeUrl.startsWith('https:')) {\n                // Still, log the incident for debugging purposes.\n                this.debugger.log(`Ignoring passive mixed content request: Driver.fetch(${req.url})`);\n                return;\n            }\n            // When opening DevTools in Chrome, a request is made for the current URL (and possibly related\n            // resources, e.g. scripts) with `cache: 'only-if-cached'` and `mode: 'no-cors'`. These request\n            // will eventually fail, because `only-if-cached` is only allowed to be used with\n            // `mode: 'same-origin'`.\n            // This is likely a bug in Chrome DevTools. Avoid handling such requests.\n            // (See also https://github.com/angular/angular/issues/22362.)\n            // TODO(gkalpak): Remove once no longer necessary (i.e. fixed in Chrome DevTools).\n            if (req.cache === 'only-if-cached' && req.mode !== 'same-origin') {\n                // Log the incident only the first time it happens, to avoid spamming the logs.\n                if (!this.loggedInvalidOnlyIfCachedRequest) {\n                    this.loggedInvalidOnlyIfCachedRequest = true;\n                    this.debugger.log(`Ignoring invalid request: 'only-if-cached' can be set only with 'same-origin' mode`, `Driver.fetch(${req.url}, cache: ${req.cache}, mode: ${req.mode})`);\n                }\n                return;\n            }\n            // Past this point, the SW commits to handling the request itself. This could still\n            // fail (and result in `state` being set to `SAFE_MODE`), but even in that case the\n            // SW will still deliver a response.\n            event.respondWith(this.handleFetch(event));\n        }\n        /**\n         * The handler for message events.\n         */\n        onMessage(event) {\n            // Ignore message events when the SW is in safe mode, for now.\n            if (this.state === DriverReadyState.SAFE_MODE) {\n                return;\n            }\n            // If the message doesn't have the expected signature, ignore it.\n            const data = event.data;\n            if (!data || !data.action) {\n                return;\n            }\n            event.waitUntil((() => __awaiter$5(this, void 0, void 0, function* () {\n                // Initialization is the only event which is sent directly from the SW to itself, and thus\n                // `event.source` is not a `Client`. Handle it here, before the check for `Client` sources.\n                if (data.action === 'INITIALIZE') {\n                    return this.ensureInitialized(event);\n                }\n                // Only messages from true clients are accepted past this point.\n                // This is essentially a typecast.\n                if (!this.adapter.isClient(event.source)) {\n                    return;\n                }\n                // Handle the message and keep the SW alive until it's handled.\n                yield this.ensureInitialized(event);\n                yield this.handleMessage(data, event.source);\n            }))());\n        }\n        onPush(msg) {\n            // Push notifications without data have no effect.\n            if (!msg.data) {\n                return;\n            }\n            // Handle the push and keep the SW alive until it's handled.\n            msg.waitUntil(this.handlePush(msg.data.json()));\n        }\n        onClick(event) {\n            // Handle the click event and keep the SW alive until it's handled.\n            event.waitUntil(this.handleClick(event.notification, event.action));\n        }\n        ensureInitialized(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Since the SW may have just been started, it may or may not have been initialized already.\n                // `this.initialized` will be `null` if initialization has not yet been attempted, or will be a\n                // `Promise` which will resolve (successfully or unsuccessfully) if it has.\n                if (this.initialized !== null) {\n                    return this.initialized;\n                }\n                // Initialization has not yet been attempted, so attempt it. This should only ever happen once\n                // per SW instantiation.\n                try {\n                    this.initialized = this.initialize();\n                    yield this.initialized;\n                }\n                catch (error) {\n                    // If initialization fails, the SW needs to enter a safe state, where it declines to respond\n                    // to network requests.\n                    this.state = DriverReadyState.SAFE_MODE;\n                    this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`;\n                    throw error;\n                }\n                finally {\n                    // Regardless if initialization succeeded, background tasks still need to happen.\n                    event.waitUntil(this.idle.trigger());\n                }\n            });\n        }\n        handleMessage(msg, from) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                if (isMsgCheckForUpdates(msg)) {\n                    const action = (() => __awaiter$5(this, void 0, void 0, function* () { yield this.checkForUpdate(); }))();\n                    yield this.reportStatus(from, action, msg.statusNonce);\n                }\n                else if (isMsgActivateUpdate(msg)) {\n                    yield this.reportStatus(from, this.updateClient(from), msg.statusNonce);\n                }\n            });\n        }\n        handlePush(data) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.broadcast({\n                    type: 'PUSH',\n                    data,\n                });\n                if (!data.notification || !data.notification.title) {\n                    return;\n                }\n                const desc = data.notification;\n                let options = {};\n                NOTIFICATION_OPTION_NAMES.filter(name => desc.hasOwnProperty(name))\n                    .forEach(name => options[name] = desc[name]);\n                yield this.scope.registration.showNotification(desc['title'], options);\n            });\n        }\n        handleClick(notification, action) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                notification.close();\n                const options = {};\n                // The filter uses `name in notification` because the properties are on the prototype so\n                // hasOwnProperty does not work here\n                NOTIFICATION_OPTION_NAMES.filter(name => name in notification)\n                    .forEach(name => options[name] = notification[name]);\n                yield this.broadcast({\n                    type: 'NOTIFICATION_CLICK',\n                    data: { action, notification: options },\n                });\n            });\n        }\n        reportStatus(client, promise, nonce) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const response = { type: 'STATUS', nonce, status: true };\n                try {\n                    yield promise;\n                    client.postMessage(response);\n                }\n                catch (e) {\n                    client.postMessage(Object.assign(Object.assign({}, response), { status: false, error: e.toString() }));\n                }\n            });\n        }\n        updateClient(client) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Figure out which version the client is on. If it's not on the latest,\n                // it needs to be moved.\n                const existing = this.clientVersionMap.get(client.id);\n                if (existing === this.latestHash) {\n                    // Nothing to do, this client is already on the latest version.\n                    return;\n                }\n                // Switch the client over.\n                let previous = undefined;\n                // Look up the application data associated with the existing version. If there\n                // isn't any, fall back on using the hash.\n                if (existing !== undefined) {\n                    const existingVersion = this.versions.get(existing);\n                    previous = this.mergeHashWithAppData(existingVersion.manifest, existing);\n                }\n                // Set the current version used by the client, and sync the mapping to disk.\n                this.clientVersionMap.set(client.id, this.latestHash);\n                yield this.sync();\n                // Notify the client about this activation.\n                const current = this.versions.get(this.latestHash);\n                const notice = {\n                    type: 'UPDATE_ACTIVATED',\n                    previous,\n                    current: this.mergeHashWithAppData(current.manifest, this.latestHash),\n                };\n                client.postMessage(notice);\n            });\n        }\n        handleFetch(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                try {\n                    // Ensure the SW instance has been initialized.\n                    yield this.ensureInitialized(event);\n                }\n                catch (_a) {\n                    // Since the SW is already committed to responding to the currently active request,\n                    // respond with a network fetch.\n                    return this.safeFetch(event.request);\n                }\n                // On navigation requests, check for new updates.\n                if (event.request.mode === 'navigate' && !this.scheduledNavUpdateCheck) {\n                    this.scheduledNavUpdateCheck = true;\n                    this.idle.schedule('check-updates-on-navigation', () => __awaiter$5(this, void 0, void 0, function* () {\n                        this.scheduledNavUpdateCheck = false;\n                        yield this.checkForUpdate();\n                    }));\n                }\n                // Decide which version of the app to use to serve this request. This is asynchronous as in\n                // some cases, a record will need to be written to disk about the assignment that is made.\n                const appVersion = yield this.assignVersion(event);\n                // Bail out\n                if (appVersion === null) {\n                    event.waitUntil(this.idle.trigger());\n                    return this.safeFetch(event.request);\n                }\n                let res = null;\n                try {\n                    // Handle the request. First try the AppVersion. If that doesn't work, fall back on the\n                    // network.\n                    res = yield appVersion.handleFetch(event.request, event);\n                }\n                catch (err) {\n                    if (err.isCritical) {\n                        // Something went wrong with the activation of this version.\n                        yield this.versionFailed(appVersion, err);\n                        event.waitUntil(this.idle.trigger());\n                        return this.safeFetch(event.request);\n                    }\n                    throw err;\n                }\n                // The AppVersion will only return null if the manifest doesn't specify what to do about this\n                // request. In that case, just fall back on the network.\n                if (res === null) {\n                    event.waitUntil(this.idle.trigger());\n                    return this.safeFetch(event.request);\n                }\n                // Trigger the idle scheduling system. The Promise returned by trigger() will resolve after\n                // a specific amount of time has passed. If trigger() hasn't been called again by then (e.g.\n                // on a subsequent request), the idle task queue will be drained and the Promise won't resolve\n                // until that operation is complete as well.\n                event.waitUntil(this.idle.trigger());\n                // The AppVersion returned a usable response, so return it.\n                return res;\n            });\n        }\n        /**\n         * Attempt to quickly reach a state where it's safe to serve responses.\n         */\n        initialize() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // On initialization, all of the serialized state is read out of the 'control'\n                // table. This includes:\n                // - map of hashes to manifests of currently loaded application versions\n                // - map of client IDs to their pinned versions\n                // - record of the most recently fetched manifest hash\n                //\n                // If these values don't exist in the DB, then this is the either the first time\n                // the SW has run or the DB state has been wiped or is inconsistent. In that case,\n                // load a fresh copy of the manifest and reset the state from scratch.\n                // Open up the DB table.\n                const table = yield this.db.open('control');\n                // Attempt to load the needed state from the DB. If this fails, the catch {} block\n                // will populate these variables with freshly constructed values.\n                let manifests, assignments, latest;\n                try {\n                    // Read them from the DB simultaneously.\n                    [manifests, assignments, latest] = yield Promise.all([\n                        table.read('manifests'),\n                        table.read('assignments'),\n                        table.read('latest'),\n                    ]);\n                    // Successfully loaded from saved state. This implies a manifest exists, so\n                    // the update check needs to happen in the background.\n                    this.idle.schedule('init post-load (update, cleanup)', () => __awaiter$5(this, void 0, void 0, function* () {\n                        yield this.checkForUpdate();\n                        try {\n                            yield this.cleanupCaches();\n                        }\n                        catch (err) {\n                            // Nothing to do - cleanup failed. Just log it.\n                            this.debugger.log(err, 'cleanupCaches @ init post-load');\n                        }\n                    }));\n                }\n                catch (_) {\n                    // Something went wrong. Try to start over by fetching a new manifest from the\n                    // server and building up an empty initial state.\n                    const manifest = yield this.fetchLatestManifest();\n                    const hash = hashManifest(manifest);\n                    manifests = {};\n                    manifests[hash] = manifest;\n                    assignments = {};\n                    latest = { latest: hash };\n                    // Save the initial state to the DB.\n                    yield Promise.all([\n                        table.write('manifests', manifests),\n                        table.write('assignments', assignments),\n                        table.write('latest', latest),\n                    ]);\n                }\n                // At this point, either the state has been loaded successfully, or fresh state\n                // with a new copy of the manifest has been produced. At this point, the `Driver`\n                // can have its internals hydrated from the state.\n                // Initialize the `versions` map by setting each hash to a new `AppVersion` instance\n                // for that manifest.\n                Object.keys(manifests).forEach((hash) => {\n                    const manifest = manifests[hash];\n                    // If the manifest is newly initialized, an AppVersion may have already been\n                    // created for it.\n                    if (!this.versions.has(hash)) {\n                        this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash));\n                    }\n                });\n                // Map each client ID to its associated hash. Along the way, verify that the hash\n                // is still valid for that client ID. It should not be possible for a client to\n                // still be associated with a hash that was since removed from the state.\n                Object.keys(assignments).forEach((clientId) => {\n                    const hash = assignments[clientId];\n                    if (this.versions.has(hash)) {\n                        this.clientVersionMap.set(clientId, hash);\n                    }\n                    else {\n                        this.clientVersionMap.set(clientId, latest.latest);\n                        this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`);\n                    }\n                });\n                // Set the latest version.\n                this.latestHash = latest.latest;\n                // Finally, assert that the latest version is in fact loaded.\n                if (!this.versions.has(latest.latest)) {\n                    throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`);\n                }\n                // Finally, wait for the scheduling of initialization of all versions in the\n                // manifest. Ordinarily this just schedules the initializations to happen during\n                // the next idle period, but in development mode this might actually wait for the\n                // full initialization.\n                // If any of these initializations fail, versionFailed() will be called either\n                // synchronously or asynchronously to handle the failure and re-map clients.\n                yield Promise.all(Object.keys(manifests).map((hash) => __awaiter$5(this, void 0, void 0, function* () {\n                    try {\n                        // Attempt to schedule or initialize this version. If this operation is\n                        // successful, then initialization either succeeded or was scheduled. If\n                        // it fails, then full initialization was attempted and failed.\n                        yield this.scheduleInitialization(this.versions.get(hash));\n                    }\n                    catch (err) {\n                        this.debugger.log(err, `initialize: schedule init of ${hash}`);\n                        return false;\n                    }\n                })));\n            });\n        }\n        lookupVersionByHash(hash, debugName = 'lookupVersionByHash') {\n            // The version should exist, but check just in case.\n            if (!this.versions.has(hash)) {\n                throw new Error(`Invariant violated (${debugName}): want AppVersion for ${hash} but not loaded`);\n            }\n            return this.versions.get(hash);\n        }\n        /**\n         * Decide which version of the manifest to use for the event.\n         */\n        assignVersion(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // First, check whether the event has a (non empty) client ID. If it does, the version may\n                // already be associated.\n                const clientId = event.clientId;\n                if (clientId) {\n                    // Check if there is an assigned client id.\n                    if (this.clientVersionMap.has(clientId)) {\n                        // There is an assignment for this client already.\n                        const hash = this.clientVersionMap.get(clientId);\n                        let appVersion = this.lookupVersionByHash(hash, 'assignVersion');\n                        // Ordinarily, this client would be served from its assigned version. But, if this\n                        // request is a navigation request, this client can be updated to the latest\n                        // version immediately.\n                        if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash &&\n                            appVersion.isNavigationRequest(event.request)) {\n                            // Update this client to the latest version immediately.\n                            if (this.latestHash === null) {\n                                throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                            }\n                            const client = yield this.scope.clients.get(clientId);\n                            yield this.updateClient(client);\n                            appVersion = this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                        }\n                        // TODO: make sure the version is valid.\n                        return appVersion;\n                    }\n                    else {\n                        // This is the first time this client ID has been seen. Whether the SW is in a\n                        // state to handle new clients depends on the current readiness state, so check\n                        // that first.\n                        if (this.state !== DriverReadyState.NORMAL) {\n                            // It's not safe to serve new clients in the current state. It's possible that\n                            // this is an existing client which has not been mapped yet (see below) but\n                            // even if that is the case, it's invalid to make an assignment to a known\n                            // invalid version, even if that assignment was previously implicit. Return\n                            // undefined here to let the caller know that no assignment is possible at\n                            // this time.\n                            return null;\n                        }\n                        // It's safe to handle this request. Two cases apply. Either:\n                        // 1) the browser assigned a client ID at the time of the navigation request, and\n                        //    this is truly the first time seeing this client, or\n                        // 2) a navigation request came previously from the same client, but with no client\n                        //    ID attached. Browsers do this to avoid creating a client under the origin in\n                        //    the event the navigation request is just redirected.\n                        //\n                        // In case 1, the latest version can safely be used.\n                        // In case 2, the latest version can be used, with the assumption that the previous\n                        // navigation request was answered under the same version. This assumption relies\n                        // on the fact that it's unlikely an update will come in between the navigation\n                        // request and requests for subsequent resources on that page.\n                        // First validate the current state.\n                        if (this.latestHash === null) {\n                            throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                        }\n                        // Pin this client ID to the current latest version, indefinitely.\n                        this.clientVersionMap.set(clientId, this.latestHash);\n                        yield this.sync();\n                        // Return the latest `AppVersion`.\n                        return this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                    }\n                }\n                else {\n                    // No client ID was associated with the request. This must be a navigation request\n                    // for a new client. First check that the SW is accepting new clients.\n                    if (this.state !== DriverReadyState.NORMAL) {\n                        return null;\n                    }\n                    // Serve it with the latest version, and assume that the client will actually get\n                    // associated with that version on the next request.\n                    // First validate the current state.\n                    if (this.latestHash === null) {\n                        throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                    }\n                    // Return the latest `AppVersion`.\n                    return this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                }\n            });\n        }\n        fetchLatestManifest(ignoreOfflineError = false) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const res = yield this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));\n                if (!res.ok) {\n                    if (res.status === 404) {\n                        yield this.deleteAllCaches();\n                        yield this.scope.registration.unregister();\n                    }\n                    else if (res.status === 504 && ignoreOfflineError) {\n                        return null;\n                    }\n                    throw new Error(`Manifest fetch failed! (status: ${res.status})`);\n                }\n                this.lastUpdateCheck = this.adapter.time;\n                return res.json();\n            });\n        }\n        deleteAllCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield (yield this.scope.caches.keys())\n                    .filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:`))\n                    .reduce((previous, key) => __awaiter$5(this, void 0, void 0, function* () {\n                    yield Promise.all([\n                        previous,\n                        this.scope.caches.delete(key),\n                    ]);\n                }), Promise.resolve());\n            });\n        }\n        /**\n         * Schedule the SW's attempt to reach a fully prefetched state for the given AppVersion\n         * when the SW is not busy and has connectivity. This returns a Promise which must be\n         * awaited, as under some conditions the AppVersion might be initialized immediately.\n         */\n        scheduleInitialization(appVersion) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const initialize = () => __awaiter$5(this, void 0, void 0, function* () {\n                    try {\n                        yield appVersion.initializeFully();\n                    }\n                    catch (err) {\n                        this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`);\n                        yield this.versionFailed(appVersion, err);\n                    }\n                });\n                // TODO: better logic for detecting localhost.\n                if (this.scope.registration.scope.indexOf('://localhost') > -1) {\n                    return initialize();\n                }\n                this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize);\n            });\n        }\n        versionFailed(appVersion, err) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // This particular AppVersion is broken. First, find the manifest hash.\n                const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);\n                if (broken === undefined) {\n                    // This version is no longer in use anyway, so nobody cares.\n                    return;\n                }\n                const brokenHash = broken[0];\n                const affectedClients = Array.from(this.clientVersionMap.entries())\n                    .filter(([clientId, hash]) => hash === brokenHash)\n                    .map(([clientId]) => clientId);\n                // TODO: notify affected apps.\n                // The action taken depends on whether the broken manifest is the active (latest) or not.\n                // If so, the SW cannot accept new clients, but can continue to service old ones.\n                if (this.latestHash === brokenHash) {\n                    // The latest manifest is broken. This means that new clients are at the mercy of the\n                    // network, but caches continue to be valid for previous versions. This is\n                    // unfortunate but unavoidable.\n                    this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;\n                    this.stateMessage = `Degraded due to: ${errorToString(err)}`;\n                    // Cancel the binding for the affected clients.\n                    affectedClients.forEach(clientId => this.clientVersionMap.delete(clientId));\n                }\n                else {\n                    // The latest version is viable, but this older version isn't. The only\n                    // possible remedy is to stop serving the older version and go to the network.\n                    // Put the affected clients on the latest version.\n                    affectedClients.forEach(clientId => this.clientVersionMap.set(clientId, this.latestHash));\n                }\n                try {\n                    yield this.sync();\n                }\n                catch (err2) {\n                    // We are already in a bad state. No need to make things worse.\n                    // Just log the error and move on.\n                    this.debugger.log(err2, `Driver.versionFailed(${err.message || err})`);\n                }\n            });\n        }\n        setupUpdate(manifest, hash) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash);\n                // Firstly, check if the manifest version is correct.\n                if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {\n                    yield this.deleteAllCaches();\n                    yield this.scope.registration.unregister();\n                    throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`);\n                }\n                // Cause the new version to become fully initialized. If this fails, then the\n                // version will not be available for use.\n                yield newVersion.initializeFully(this);\n                // Install this as an active version of the app.\n                this.versions.set(hash, newVersion);\n                // Future new clients will use this hash as the latest version.\n                this.latestHash = hash;\n                // If we are in `EXISTING_CLIENTS_ONLY` mode (meaning we didn't have a clean copy of the last\n                // latest version), we can now recover to `NORMAL` mode and start accepting new clients.\n                if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) {\n                    this.state = DriverReadyState.NORMAL;\n                    this.stateMessage = '(nominal)';\n                }\n                yield this.sync();\n                yield this.notifyClientsAboutUpdate(newVersion);\n            });\n        }\n        checkForUpdate() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                let hash = '(unknown)';\n                try {\n                    const manifest = yield this.fetchLatestManifest(true);\n                    if (manifest === null) {\n                        // Client or server offline. Unable to check for updates at this time.\n                        // Continue to service clients (existing and new).\n                        this.debugger.log('Check for update aborted. (Client or server offline.)');\n                        return false;\n                    }\n                    hash = hashManifest(manifest);\n                    // Check whether this is really an update.\n                    if (this.versions.has(hash)) {\n                        return false;\n                    }\n                    yield this.setupUpdate(manifest, hash);\n                    return true;\n                }\n                catch (err) {\n                    this.debugger.log(err, `Error occurred while updating to manifest ${hash}`);\n                    this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;\n                    this.stateMessage = `Degraded due to failed initialization: ${errorToString(err)}`;\n                    return false;\n                }\n            });\n        }\n        /**\n         * Synchronize the existing state to the underlying database.\n         */\n        sync() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Open up the DB table.\n                const table = yield this.db.open('control');\n                // Construct a serializable map of hashes to manifests.\n                const manifests = {};\n                this.versions.forEach((version, hash) => { manifests[hash] = version.manifest; });\n                // Construct a serializable map of client ids to version hashes.\n                const assignments = {};\n                this.clientVersionMap.forEach((hash, clientId) => { assignments[clientId] = hash; });\n                // Record the latest entry. Since this is a sync which is necessarily happening after\n                // initialization, latestHash should always be valid.\n                const latest = {\n                    latest: this.latestHash,\n                };\n                // Synchronize all of these.\n                yield Promise.all([\n                    table.write('manifests', manifests),\n                    table.write('assignments', assignments),\n                    table.write('latest', latest),\n                ]);\n            });\n        }\n        cleanupCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Query for all currently active clients, and list the client ids. This may skip\n                // some clients in the browser back-forward cache, but not much can be done about\n                // that.\n                const activeClients = (yield this.scope.clients.matchAll()).map(client => client.id);\n                // A simple list of client ids that the SW has kept track of. Subtracting\n                // activeClients from this list will result in the set of client ids which are\n                // being tracked but are no longer used in the browser, and thus can be cleaned up.\n                const knownClients = Array.from(this.clientVersionMap.keys());\n                // Remove clients in the clientVersionMap that are no longer active.\n                knownClients.filter(id => activeClients.indexOf(id) === -1)\n                    .forEach(id => this.clientVersionMap.delete(id));\n                // Next, determine the set of versions which are still used. All others can be\n                // removed.\n                const usedVersions = new Set();\n                this.clientVersionMap.forEach((version, _) => usedVersions.add(version));\n                // Collect all obsolete versions by filtering out used versions from the set of all versions.\n                const obsoleteVersions = Array.from(this.versions.keys())\n                    .filter(version => !usedVersions.has(version) && version !== this.latestHash);\n                // Remove all the versions which are no longer used.\n                yield obsoleteVersions.reduce((previous, version) => __awaiter$5(this, void 0, void 0, function* () {\n                    // Wait for the other cleanup operations to complete.\n                    yield previous;\n                    // Try to get past the failure of one particular version to clean up (this\n                    // shouldn't happen, but handle it just in case).\n                    try {\n                        // Get ahold of the AppVersion for this particular hash.\n                        const instance = this.versions.get(version);\n                        // Delete it from the canonical map.\n                        this.versions.delete(version);\n                        // Clean it up.\n                        yield instance.cleanup();\n                    }\n                    catch (err) {\n                        // Oh well? Not much that can be done here. These caches will be removed when\n                        // the SW revs its format version, which happens from time to time.\n                        this.debugger.log(err, `cleanupCaches - cleanup ${version}`);\n                    }\n                }), Promise.resolve());\n                // Commit all the changes to the saved state.\n                yield this.sync();\n            });\n        }\n        /**\n         * Delete caches that were used by older versions of `@angular/service-worker` to avoid running\n         * into storage quota limitations imposed by browsers.\n         * (Since at this point the SW has claimed all clients, it is safe to remove those caches.)\n         */\n        cleanupOldSwCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const cacheNames = yield this.scope.caches.keys();\n                const oldSwCacheNames = cacheNames.filter(name => /^ngsw:(?!\\/)/.test(name));\n                yield Promise.all(oldSwCacheNames.map(name => this.scope.caches.delete(name)));\n            });\n        }\n        /**\n         * Determine if a specific version of the given resource is cached anywhere within the SW,\n         * and fetch it if so.\n         */\n        lookupResourceWithHash(url, hash) {\n            return Array\n                // Scan through the set of all cached versions, valid or otherwise. It's safe to do such\n                // lookups even for invalid versions as the cached version of a resource will have the\n                // same hash regardless.\n                .from(this.versions.values())\n                // Reduce the set of versions to a single potential result. At any point along the\n                // reduction, if a response has already been identified, then pass it through, as no\n                // future operation could change the response. If no response has been found yet, keep\n                // checking versions until one is or until all versions have been exhausted.\n                .reduce((prev, version) => __awaiter$5(this, void 0, void 0, function* () {\n                // First, check the previous result. If a non-null result has been found already, just\n                // return it.\n                if ((yield prev) !== null) {\n                    return prev;\n                }\n                // No result has been found yet. Try the next `AppVersion`.\n                return version.lookupResourceWithHash(url, hash);\n            }), Promise.resolve(null));\n        }\n        lookupResourceWithoutHash(url) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const version = this.versions.get(this.latestHash);\n                return version ? version.lookupResourceWithoutHash(url) : null;\n            });\n        }\n        previouslyCachedResources() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const version = this.versions.get(this.latestHash);\n                return version ? version.previouslyCachedResources() : [];\n            });\n        }\n        recentCacheStatus(url) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const version = this.versions.get(this.latestHash);\n                return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED;\n            });\n        }\n        mergeHashWithAppData(manifest, hash) {\n            return {\n                hash,\n                appData: manifest.appData,\n            };\n        }\n        notifyClientsAboutUpdate(next) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const clients = yield this.scope.clients.matchAll();\n                yield clients.reduce((previous, client) => __awaiter$5(this, void 0, void 0, function* () {\n                    yield previous;\n                    // Firstly, determine which version this client is on.\n                    const version = this.clientVersionMap.get(client.id);\n                    if (version === undefined) {\n                        // Unmapped client - assume it's the latest.\n                        return;\n                    }\n                    if (version === this.latestHash) {\n                        // Client is already on the latest version, no need for a notification.\n                        return;\n                    }\n                    const current = this.versions.get(version);\n                    // Send a notice.\n                    const notice = {\n                        type: 'UPDATE_AVAILABLE',\n                        current: this.mergeHashWithAppData(current.manifest, version),\n                        available: this.mergeHashWithAppData(next.manifest, this.latestHash),\n                    };\n                    client.postMessage(notice);\n                }), Promise.resolve());\n            });\n        }\n        broadcast(msg) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const clients = yield this.scope.clients.matchAll();\n                clients.forEach(client => { client.postMessage(msg); });\n            });\n        }\n        debugState() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                return {\n                    state: DriverReadyState[this.state],\n                    why: this.stateMessage,\n                    latestHash: this.latestHash,\n                    lastUpdateCheck: this.lastUpdateCheck,\n                };\n            });\n        }\n        debugVersions() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Build list of versions.\n                return Array.from(this.versions.keys()).map(hash => {\n                    const version = this.versions.get(hash);\n                    const clients = Array.from(this.clientVersionMap.entries())\n                        .filter(([clientId, version]) => version === hash)\n                        .map(([clientId, version]) => clientId);\n                    return {\n                        hash,\n                        manifest: version.manifest, clients,\n                        status: '',\n                    };\n                });\n            });\n        }\n        debugIdleState() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                return {\n                    queue: this.idle.taskDescriptions,\n                    lastTrigger: this.idle.lastTrigger,\n                    lastRun: this.idle.lastRun,\n                };\n            });\n        }\n        safeFetch(req) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                try {\n                    return yield this.scope.fetch(req);\n                }\n                catch (err) {\n                    this.debugger.log(err, `Driver.fetch(${req.url})`);\n                    return this.adapter.newResponse(null, {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    const scope = self;\n    const adapter = new Adapter(scope);\n    const driver = new Driver(scope, adapter, new CacheDatabase(scope, adapter));\n\n}());\n"
  },
  {
    "path": "Chapter_11/HealthCheck/wwwroot/ngsw.json",
    "content": "{\n  \"configVersion\": 1,\n  \"timestamp\": 1577992220985,\n  \"index\": \"/index.html\",\n  \"assetGroups\": [\n    {\n      \"name\": \"app\",\n      \"installMode\": \"prefetch\",\n      \"updateMode\": \"prefetch\",\n      \"urls\": [\n        \"/index.html\",\n        \"/main-es2015.0dfa6088d7ebe21b943a.js\",\n        \"/main-es5.0dfa6088d7ebe21b943a.js\",\n        \"/manifest.webmanifest\",\n        \"/polyfills-es2015.58725a5910daef768ca8.js\",\n        \"/polyfills-es5.079443d8bcab7d711023.js\",\n        \"/runtime-es2015.e59a6cd8f1b6ab0c3f29.js\",\n        \"/runtime-es5.e59a6cd8f1b6ab0c3f29.js\",\n        \"/styles.519f5f6cb11c0ac03ff3.css\"\n      ],\n      \"patterns\": []\n    },\n    {\n      \"name\": \"assets\",\n      \"installMode\": \"lazy\",\n      \"updateMode\": \"prefetch\",\n      \"urls\": [\n        \"/assets/icons/icon-128x128.png\",\n        \"/assets/icons/icon-144x144.png\",\n        \"/assets/icons/icon-152x152.png\",\n        \"/assets/icons/icon-192x192.png\",\n        \"/assets/icons/icon-384x384.png\",\n        \"/assets/icons/icon-512x512.png\",\n        \"/assets/icons/icon-72x72.png\",\n        \"/assets/icons/icon-96x96.png\"\n      ],\n      \"patterns\": []\n    }\n  ],\n  \"dataGroups\": [],\n  \"hashTable\": {\n    \"/assets/icons/icon-128x128.png\": \"664808390a86b765b38840d2229bb8e77f23315f\",\n    \"/assets/icons/icon-144x144.png\": \"7d2df9f8dfadc3ba692838dc5a05fdda171ffe46\",\n    \"/assets/icons/icon-152x152.png\": \"328dddc298922fee24ca15f6188a8be20b1b5e85\",\n    \"/assets/icons/icon-192x192.png\": \"36caee50fcc2ddeca25acd0a74ccb12a04281dd2\",\n    \"/assets/icons/icon-384x384.png\": \"f3fe7601922932a95550a3fa767efec8eb2c54f2\",\n    \"/assets/icons/icon-512x512.png\": \"8e215e2e14a8fe8ed23ad313003a2da652b0acd9\",\n    \"/assets/icons/icon-72x72.png\": \"cbf6ac39ff640851fd721545fa68595aaf715e50\",\n    \"/assets/icons/icon-96x96.png\": \"745d24bafdf890ad4f63e9c4e69c713212849802\",\n    \"/index.html\": \"b73c833cf32b543e091e8683ded1744c3d7e0c76\",\n    \"/main-es2015.0dfa6088d7ebe21b943a.js\": \"2676c9e57672d9cec97ee29945ab8a60cf7dc916\",\n    \"/main-es5.0dfa6088d7ebe21b943a.js\": \"9332386d541e52f5269f53ca4be1aa8aa4576841\",\n    \"/manifest.webmanifest\": \"43ccf4b7574d245892754a78dcf31cfa4e2ef956\",\n    \"/polyfills-es2015.58725a5910daef768ca8.js\": \"3e29b382a75f751f979baff5606aeb37d3f66c3a\",\n    \"/polyfills-es5.079443d8bcab7d711023.js\": \"f2bfdffc3174ace137ac2516e8682571b6f460b0\",\n    \"/runtime-es2015.e59a6cd8f1b6ab0c3f29.js\": \"a9aafcf49f49145093fc831efd9b8e2f6c71bb9c\",\n    \"/runtime-es5.e59a6cd8f1b6ab0c3f29.js\": \"a9aafcf49f49145093fc831efd9b8e2f6c71bb9c\",\n    \"/styles.519f5f6cb11c0ac03ff3.css\": \"722333c0e596cfbb27969f7ac040dcef942da1ca\"\n  },\n  \"navigationUrls\": [\n    {\n      \"positive\": true,\n      \"regex\": \"^\\\\/.*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*\\\\.[^/]*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*__[^/]*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*__[^/]*\\\\/.*$\"\n    }\n  ]\n}"
  },
  {
    "path": "Chapter_11/HealthCheck/wwwroot/safety-worker.js",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\n\n// tslint:disable:no-console\n\nself.addEventListener('install', event => { self.skipWaiting(); });\n\nself.addEventListener('activate', event => {\n  event.waitUntil(self.clients.claim());\n  self.registration.unregister().then(\n      () => { console.log('NGSW Safety Worker - unregistered old service worker'); });\n});\n"
  },
  {
    "path": "Chapter_11/HealthCheck/wwwroot/test.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\" />\n    <title>Time for a test!</title>\n</head>\n<body>\n    Hello there!\n    <br /><br />\n    This is a test to see if the StaticFiles middleware is working properly.\n    <br /><br />\n    What about the client-side cache? Does it work or not?\n    <br /><br />\n    It seems like we can configure it: we disabled it during development,\n      and enabled it in production!\n</body>\n</html>"
  },
  {
    "path": "Chapter_11/WorldCities/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/README.md",
    "content": "# WorldCities\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"WorldCities\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\n              \"src/assets\",\n              \"src/manifest.webmanifest\"\n            ],\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true,\n              \"serviceWorker\": true,\n              \"ngswConfigPath\": \"ngsw-config.json\"\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"WorldCities:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": [],\n            \"assets\": [\n              \"src/assets\",\n              \"src/manifest.webmanifest\"\n            ]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\n              \"src/tsconfig.app.json\",\n              \"src/tsconfig.spec.json\"\n            ],\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"WorldCities-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"WorldCities:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"WorldCities\"\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/ngsw-config.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/service-worker/config/schema.json\",\n  \"index\": \"/index.html\",\n  \"assetGroups\": [\n    {\n      \"name\": \"app\",\n      \"installMode\": \"prefetch\",\n      \"resources\": {\n        \"files\": [\n          \"/favicon.ico\",\n          \"/index.html\",\n          \"/manifest.webmanifest\",\n          \"/*.css\",\n          \"/*.js\"\n        ]\n      }\n    }, {\n      \"name\": \"assets\",\n      \"installMode\": \"lazy\",\n      \"updateMode\": \"prefetch\",\n      \"resources\": {\n        \"files\": [\n          \"/assets/**\",\n          \"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)\"\n        ]\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/package.json",
    "content": "{\n  \"name\": \"worldcities\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run WorldCities:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/cdk\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/material\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/pwa\": \"^0.803.21\",\n    \"@angular/router\": \"9.0.0\",\n    \"@angular/service-worker\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"hammerjs\": \"2.0.8\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/api-authorization.constants.ts",
    "content": "export const ApplicationName = 'WorldCities';\n\nexport const ReturnUrlType = 'returnUrl';\n\nexport const QueryParameterNames = {\n  ReturnUrl: ReturnUrlType,\n  Message: 'message'\n};\n\nexport const LogoutActions = {\n  LogoutCallback: 'logout-callback',\n  Logout: 'logout',\n  LoggedOut: 'logged-out'\n};\n\nexport const LoginActions = {\n  Login: 'login',\n  LoginCallback: 'login-callback',\n  LoginFailed: 'login-failed',\n  Profile: 'profile',\n  Register: 'register'\n};\n\nlet applicationPaths: ApplicationPathsType = {\n  DefaultLoginRedirectPath: '/',\n  ApiAuthorizationClientConfigurationUrl: `/_configuration/${ApplicationName}`,\n  Login: `authentication/${LoginActions.Login}`,\n  LoginFailed: `authentication/${LoginActions.LoginFailed}`,\n  LoginCallback: `authentication/${LoginActions.LoginCallback}`,\n  Register: `authentication/${LoginActions.Register}`,\n  Profile: `authentication/${LoginActions.Profile}`,\n  LogOut: `authentication/${LogoutActions.Logout}`,\n  LoggedOut: `authentication/${LogoutActions.LoggedOut}`,\n  LogOutCallback: `authentication/${LogoutActions.LogoutCallback}`,\n  LoginPathComponents: [],\n  LoginFailedPathComponents: [],\n  LoginCallbackPathComponents: [],\n  RegisterPathComponents: [],\n  ProfilePathComponents: [],\n  LogOutPathComponents: [],\n  LoggedOutPathComponents: [],\n  LogOutCallbackPathComponents: [],\n  IdentityRegisterPath: '/Identity/Account/Register',\n  IdentityManagePath: '/Identity/Account/Manage'\n};\n\napplicationPaths = {\n  ...applicationPaths,\n  LoginPathComponents: applicationPaths.Login.split('/'),\n  LoginFailedPathComponents: applicationPaths.LoginFailed.split('/'),\n  RegisterPathComponents: applicationPaths.Register.split('/'),\n  ProfilePathComponents: applicationPaths.Profile.split('/'),\n  LogOutPathComponents: applicationPaths.LogOut.split('/'),\n  LoggedOutPathComponents: applicationPaths.LoggedOut.split('/'),\n  LogOutCallbackPathComponents: applicationPaths.LogOutCallback.split('/')\n};\n\ninterface ApplicationPathsType {\n  readonly DefaultLoginRedirectPath: string;\n  readonly ApiAuthorizationClientConfigurationUrl: string;\n  readonly Login: string;\n  readonly LoginFailed: string;\n  readonly LoginCallback: string;\n  readonly Register: string;\n  readonly Profile: string;\n  readonly LogOut: string;\n  readonly LoggedOut: string;\n  readonly LogOutCallback: string;\n  readonly LoginPathComponents: string [];\n  readonly LoginFailedPathComponents: string [];\n  readonly LoginCallbackPathComponents: string [];\n  readonly RegisterPathComponents: string [];\n  readonly ProfilePathComponents: string [];\n  readonly LogOutPathComponents: string [];\n  readonly LoggedOutPathComponents: string [];\n  readonly LogOutCallbackPathComponents: string [];\n  readonly IdentityRegisterPath: string;\n  readonly IdentityManagePath: string;\n}\n\nexport const ApplicationPaths: ApplicationPathsType = applicationPaths;\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/api-authorization.module.spec.ts",
    "content": "import { ApiAuthorizationModule } from './api-authorization.module';\n\ndescribe('ApiAuthorizationModule', () => {\n  let apiAuthorizationModule: ApiAuthorizationModule;\n\n  beforeEach(() => {\n    apiAuthorizationModule = new ApiAuthorizationModule();\n  });\n\n  it('should create an instance', () => {\n    expect(apiAuthorizationModule).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/api-authorization.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { LoginMenuComponent } from './login-menu/login-menu.component';\nimport { LoginComponent } from './login/login.component';\nimport { LogoutComponent } from './logout/logout.component';\nimport { RouterModule } from '@angular/router';\nimport { ApplicationPaths } from './api-authorization.constants';\nimport { HttpClientModule } from '@angular/common/http';\n\n@NgModule({\n  imports: [\n    CommonModule,\n    HttpClientModule,\n    RouterModule.forChild(\n      [\n        { path: ApplicationPaths.Register, component: LoginComponent },\n        { path: ApplicationPaths.Profile, component: LoginComponent },\n        { path: ApplicationPaths.Login, component: LoginComponent },\n        { path: ApplicationPaths.LoginFailed, component: LoginComponent },\n        { path: ApplicationPaths.LoginCallback, component: LoginComponent },\n        { path: ApplicationPaths.LogOut, component: LogoutComponent },\n        { path: ApplicationPaths.LoggedOut, component: LogoutComponent },\n        { path: ApplicationPaths.LogOutCallback, component: LogoutComponent }\n      ]\n    )\n  ],\n  declarations: [LoginMenuComponent, LoginComponent, LogoutComponent],\n  exports: [LoginMenuComponent, LoginComponent, LogoutComponent]\n})\nexport class ApiAuthorizationModule { }\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/authorize.guard.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeGuard } from './authorize.guard';\n\ndescribe('AuthorizeGuard', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeGuard]\n    });\n  });\n\n  it('should ...', inject([AuthorizeGuard], (guard: AuthorizeGuard) => {\n    expect(guard).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/authorize.guard.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { AuthorizeService } from './authorize.service';\nimport { tap } from 'rxjs/operators';\nimport { ApplicationPaths, QueryParameterNames } from './api-authorization.constants';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeGuard implements CanActivate {\n  constructor(private authorize: AuthorizeService, private router: Router) {\n  }\n  canActivate(\n    _next: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {\n      return this.authorize.isAuthenticated()\n        .pipe(tap(isAuthenticated => this.handleAuthorization(isAuthenticated, state)));\n  }\n\n  private handleAuthorization(isAuthenticated: boolean, state: RouterStateSnapshot) {\n    if (!isAuthenticated) {\n      this.router.navigate(ApplicationPaths.LoginPathComponents, {\n        queryParams: {\n          [QueryParameterNames.ReturnUrl]: state.url\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/authorize.interceptor.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeInterceptor } from './authorize.interceptor';\n\ndescribe('AuthorizeInterceptor', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeInterceptor]\n    });\n  });\n\n  it('should be created', inject([AuthorizeInterceptor], (service: AuthorizeInterceptor) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/authorize.interceptor.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';\nimport { Observable } from 'rxjs';\nimport { AuthorizeService } from './authorize.service';\nimport { mergeMap } from 'rxjs/operators';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeInterceptor implements HttpInterceptor {\n  constructor(private authorize: AuthorizeService) { }\n\n  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {\n    return this.authorize.getAccessToken()\n      .pipe(mergeMap(token => this.processRequestWithToken(token, req, next)));\n  }\n\n  // Checks if there is an access_token available in the authorize service\n  // and adds it to the request in case it's targeted at the same origin as the\n  // single page application.\n  private processRequestWithToken(token: string, req: HttpRequest<any>, next: HttpHandler) {\n    if (!!token && this.isSameOriginUrl(req)) {\n      req = req.clone({\n        setHeaders: {\n          Authorization: `Bearer ${token}`\n        }\n      });\n    }\n\n    return next.handle(req);\n  }\n\n  private isSameOriginUrl(req: any) {\n    // It's an absolute url with the same origin.\n    if (req.url.startsWith(`${window.location.origin}/`)) {\n      return true;\n    }\n\n    // It's a protocol relative url with the same origin.\n    // For example: //www.example.com/api/Products\n    if (req.url.startsWith(`//${window.location.host}/`)) {\n      return true;\n    }\n\n    // It's a relative url like /api/Products\n    if (/^\\/[^\\/].*/.test(req.url)) {\n      return true;\n    }\n\n    // It's an absolute or protocol relative url that\n    // doesn't have the same origin.\n    return false;\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/authorize.service.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeService } from './authorize.service';\n\ndescribe('AuthorizeService', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeService]\n    });\n  });\n\n  it('should be created', inject([AuthorizeService], (service: AuthorizeService) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/authorize.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { User, UserManager, WebStorageStateStore } from 'oidc-client';\nimport { BehaviorSubject, concat, from, Observable } from 'rxjs';\nimport { filter, map, mergeMap, take, tap } from 'rxjs/operators';\nimport { ApplicationPaths, ApplicationName } from './api-authorization.constants';\n\nexport type IAuthenticationResult =\n  SuccessAuthenticationResult |\n  FailureAuthenticationResult |\n  RedirectAuthenticationResult;\n\nexport interface SuccessAuthenticationResult {\n  status: AuthenticationResultStatus.Success;\n  state: any;\n}\n\nexport interface FailureAuthenticationResult {\n  status: AuthenticationResultStatus.Fail;\n  message: string;\n}\n\nexport interface RedirectAuthenticationResult {\n  status: AuthenticationResultStatus.Redirect;\n}\n\nexport enum AuthenticationResultStatus {\n  Success,\n  Redirect,\n  Fail\n}\n\nexport interface IUser {\n  name: string;\n}\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeService {\n  // By default pop ups are disabled because they don't work properly on Edge.\n  // If you want to enable pop up authentication simply set this flag to false.\n\n  private popUpDisabled = true;\n  private userManager: UserManager;\n  private userSubject: BehaviorSubject<IUser | null> = new BehaviorSubject(null);\n\n  public isAuthenticated(): Observable<boolean> {\n    return this.getUser().pipe(map(u => !!u));\n  }\n\n  public getUser(): Observable<IUser | null> {\n    return concat(\n      this.userSubject.pipe(take(1), filter(u => !!u)),\n      this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))),\n      this.userSubject.asObservable());\n  }\n\n  public getAccessToken(): Observable<string> {\n    return from(this.ensureUserManagerInitialized())\n      .pipe(mergeMap(() => from(this.userManager.getUser())),\n        map(user => user && user.access_token));\n  }\n\n  // We try to authenticate the user in three different ways:\n  // 1) We try to see if we can authenticate the user silently. This happens\n  //    when the user is already logged in on the IdP and is done using a hidden iframe\n  //    on the client.\n  // 2) We try to authenticate the user using a PopUp Window. This might fail if there is a\n  //    Pop-Up blocker or the user has disabled PopUps.\n  // 3) If the two methods above fail, we redirect the browser to the IdP to perform a traditional\n  //    redirect flow.\n  public async signIn(state: any): Promise<IAuthenticationResult> {\n    await this.ensureUserManagerInitialized();\n    let user: User = null;\n    try {\n      user = await this.userManager.signinSilent(this.createArguments());\n      this.userSubject.next(user.profile);\n      return this.success(state);\n    } catch (silentError) {\n      // User might not be authenticated, fallback to popup authentication\n      console.log('Silent authentication error: ', silentError);\n\n      try {\n        if (this.popUpDisabled) {\n          throw new Error('Popup disabled. Change \\'authorize.service.ts:AuthorizeService.popupDisabled\\' to false to enable it.');\n        }\n        user = await this.userManager.signinPopup(this.createArguments());\n        this.userSubject.next(user.profile);\n        return this.success(state);\n      } catch (popupError) {\n        if (popupError.message === 'Popup window closed') {\n          // The user explicitly cancelled the login action by closing an opened popup.\n          return this.error('The user closed the window.');\n        } else if (!this.popUpDisabled) {\n          console.log('Popup authentication error: ', popupError);\n        }\n\n        // PopUps might be blocked by the user, fallback to redirect\n        try {\n          await this.userManager.signinRedirect(this.createArguments(state));\n          return this.redirect();\n        } catch (redirectError) {\n          console.log('Redirect authentication error: ', redirectError);\n          return this.error(redirectError);\n        }\n      }\n    }\n  }\n\n  public async completeSignIn(url: string): Promise<IAuthenticationResult> {\n    try {\n      await this.ensureUserManagerInitialized();\n      const user = await this.userManager.signinCallback(url);\n      this.userSubject.next(user && user.profile);\n      return this.success(user && user.state);\n    } catch (error) {\n      console.log('There was an error signing in: ', error);\n      return this.error('There was an error signing in.');\n    }\n  }\n\n  public async signOut(state: any): Promise<IAuthenticationResult> {\n    try {\n      if (this.popUpDisabled) {\n        throw new Error('Popup disabled. Change \\'authorize.service.ts:AuthorizeService.popupDisabled\\' to false to enable it.');\n      }\n\n      await this.ensureUserManagerInitialized();\n      await this.userManager.signoutPopup(this.createArguments());\n      this.userSubject.next(null);\n      return this.success(state);\n    } catch (popupSignOutError) {\n      console.log('Popup signout error: ', popupSignOutError);\n      try {\n        await this.userManager.signoutRedirect(this.createArguments(state));\n        return this.redirect();\n      } catch (redirectSignOutError) {\n        console.log('Redirect signout error: ', popupSignOutError);\n        return this.error(redirectSignOutError);\n      }\n    }\n  }\n\n  public async completeSignOut(url: string): Promise<IAuthenticationResult> {\n    await this.ensureUserManagerInitialized();\n    try {\n      const state = await this.userManager.signoutCallback(url);\n      this.userSubject.next(null);\n      return this.success(state && state.data);\n    } catch (error) {\n      console.log(`There was an error trying to log out '${error}'.`);\n      return this.error(error);\n    }\n  }\n\n  private createArguments(state?: any): any {\n    return { useReplaceToNavigate: true, data: state };\n  }\n\n  private error(message: string): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Fail, message };\n  }\n\n  private success(state: any): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Success, state };\n  }\n\n  private redirect(): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Redirect };\n  }\n\n  private async ensureUserManagerInitialized(): Promise<void> {\n    if (this.userManager !== undefined) {\n      return;\n    }\n\n    const response = await fetch(ApplicationPaths.ApiAuthorizationClientConfigurationUrl);\n    if (!response.ok) {\n      throw new Error(`Could not load settings for '${ApplicationName}'`);\n    }\n\n    const settings: any = await response.json();\n    settings.automaticSilentRenew = true;\n    settings.includeIdTokenInSilentRenew = true;\n    this.userManager = new UserManager(settings);\n\n    this.userManager.events.addUserSignedOut(async () => {\n      await this.userManager.removeUser();\n      this.userSubject.next(null);\n    });\n  }\n\n  private getUserFromStorage(): Observable<IUser> {\n    return from(this.ensureUserManagerInitialized())\n      .pipe(\n        mergeMap(() => this.userManager.getUser()),\n        map(u => u && u.profile));\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/login/login.component.css",
    "content": ""
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/login/login.component.html",
    "content": "<p>{{ message | async }}</p>"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/login/login.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LoginComponent } from './login.component';\n\ndescribe('LoginComponent', () => {\n  let component: LoginComponent;\n  let fixture: ComponentFixture<LoginComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LoginComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LoginComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/login/login.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthorizeService, AuthenticationResultStatus } from '../authorize.service';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { BehaviorSubject } from 'rxjs';\nimport { LoginActions, QueryParameterNames, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants';\n\n// The main responsibility of this component is to handle the user's login process.\n// This is the starting point for the login process. Any component that needs to authenticate\n// a user can simply perform a redirect to this component with a returnUrl query parameter and\n// let the component perform the login and return back to the return url.\n@Component({\n  selector: 'app-login',\n  templateUrl: './login.component.html',\n  styleUrls: ['./login.component.css']\n})\nexport class LoginComponent implements OnInit {\n  public message = new BehaviorSubject<string>(null);\n\n  constructor(\n    private authorizeService: AuthorizeService,\n    private activatedRoute: ActivatedRoute,\n    private router: Router) { }\n\n  async ngOnInit() {\n    const action = this.activatedRoute.snapshot.url[1];\n    switch (action.path) {\n      case LoginActions.Login:\n        await this.login(this.getReturnUrl());\n        break;\n      case LoginActions.LoginCallback:\n        await this.processLoginCallback();\n        break;\n      case LoginActions.LoginFailed:\n        const message = this.activatedRoute.snapshot.queryParamMap.get(QueryParameterNames.Message);\n        this.message.next(message);\n        break;\n      case LoginActions.Profile:\n        this.redirectToProfile();\n        break;\n      case LoginActions.Register:\n        this.redirectToRegister();\n        break;\n      default:\n        throw new Error(`Invalid action '${action}'`);\n    }\n  }\n\n\n  private async login(returnUrl: string): Promise<void> {\n    const state: INavigationState = { returnUrl };\n    const result = await this.authorizeService.signIn(state);\n    this.message.next(undefined);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        break;\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(returnUrl);\n        break;\n      case AuthenticationResultStatus.Fail:\n        await this.router.navigate(ApplicationPaths.LoginFailedPathComponents, {\n          queryParams: { [QueryParameterNames.Message]: result.message }\n        });\n        break;\n      default:\n        throw new Error(`Invalid status result ${(result as any).status}.`);\n    }\n  }\n\n  private async processLoginCallback(): Promise<void> {\n    const url = window.location.href;\n    const result = await this.authorizeService.completeSignIn(url);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        // There should not be any redirects as completeSignIn never redirects.\n        throw new Error('Should not redirect.');\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(this.getReturnUrl(result.state));\n        break;\n      case AuthenticationResultStatus.Fail:\n        this.message.next(result.message);\n        break;\n    }\n  }\n\n  private redirectToRegister(): any {\n    this.redirectToApiAuthorizationPath(\n      `${ApplicationPaths.IdentityRegisterPath}?returnUrl=${encodeURI('/' + ApplicationPaths.Login)}`);\n  }\n\n  private redirectToProfile(): void {\n    this.redirectToApiAuthorizationPath(ApplicationPaths.IdentityManagePath);\n  }\n\n  private async navigateToReturnUrl(returnUrl: string) {\n    // It's important that we do a replace here so that we remove the callback uri with the\n    // fragment containing the tokens from the browser history.\n    await this.router.navigateByUrl(returnUrl, {\n      replaceUrl: true\n    });\n  }\n\n  private getReturnUrl(state?: INavigationState): string {\n    const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl;\n    // If the url is comming from the query string, check that is either\n    // a relative url or an absolute url\n    if (fromQuery &&\n      !(fromQuery.startsWith(`${window.location.origin}/`) ||\n        /\\/[^\\/].*/.test(fromQuery))) {\n      // This is an extra check to prevent open redirects.\n      throw new Error('Invalid return url. The return url needs to have the same origin as the current page.');\n    }\n    return (state && state.returnUrl) ||\n      fromQuery ||\n      ApplicationPaths.DefaultLoginRedirectPath;\n  }\n\n  private redirectToApiAuthorizationPath(apiAuthorizationPath: string) {\n    // It's important that we do a replace here so that when the user hits the back arrow on the\n    // browser they get sent back to where it was on the app instead of to an endpoint on this\n    // component.\n    const redirectUrl = `${window.location.origin}${apiAuthorizationPath}`;\n    window.location.replace(redirectUrl);\n  }\n}\n\ninterface INavigationState {\n  [ReturnUrlType]: string;\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.css",
    "content": ""
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.html",
    "content": "<ul class=\"navbar-nav\" *ngIf=\"isAuthenticated | async\">\n    <li class=\"nav-item\">\n        <a  class=\"nav-link text-dark\" [routerLink]='[\"/authentication/profile\"]' title=\"Manage\">Hello {{ userName | async }}</a>\n    </li>\n    <li class=\"nav-item\">\n        <a  class=\"nav-link text-dark\" [routerLink]='[\"/authentication/logout\"]' [state]='{ local: true }' title=\"Logout\">Logout</a>\n    </li>\n</ul>\n<ul class=\"navbar-nav\" *ngIf=\"!(isAuthenticated | async)\">\n  <li class=\"nav-item\">\n        <a class=\"nav-link text-dark\" [routerLink]='[\"/authentication/register\"]'>Register</a>\n    </li>\n    <li class=\"nav-item\">\n        <a class=\"nav-link text-dark\" [routerLink]='[\"/authentication/login\"]'>Login</a>\n    </li>\n</ul>\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LoginMenuComponent } from './login-menu.component';\n\ndescribe('LoginMenuComponent', () => {\n  let component: LoginMenuComponent;\n  let fixture: ComponentFixture<LoginMenuComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LoginMenuComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LoginMenuComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthorizeService } from '../authorize.service';\nimport { Observable } from 'rxjs';\nimport { map, tap } from 'rxjs/operators';\n\n@Component({\n  selector: 'app-login-menu',\n  templateUrl: './login-menu.component.html',\n  styleUrls: ['./login-menu.component.css']\n})\nexport class LoginMenuComponent implements OnInit {\n  public isAuthenticated: Observable<boolean>;\n  public userName: Observable<string>;\n\n  constructor(private authorizeService: AuthorizeService) { }\n\n  ngOnInit() {\n    this.isAuthenticated = this.authorizeService.isAuthenticated();\n    this.userName = this.authorizeService.getUser().pipe(map(u => u && u.name));\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/logout/logout.component.css",
    "content": ""
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/logout/logout.component.html",
    "content": "<p>{{ message | async }}</p>"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/logout/logout.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LogoutComponent } from './logout.component';\n\ndescribe('LogoutComponent', () => {\n  let component: LogoutComponent;\n  let fixture: ComponentFixture<LogoutComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LogoutComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LogoutComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/api-authorization/logout/logout.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthenticationResultStatus, AuthorizeService } from '../authorize.service';\nimport { BehaviorSubject } from 'rxjs';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { take } from 'rxjs/operators';\nimport { LogoutActions, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants';\n\n// The main responsibility of this component is to handle the user's logout process.\n// This is the starting point for the logout process, which is usually initiated when a\n// user clicks on the logout button on the LoginMenu component.\n@Component({\n  selector: 'app-logout',\n  templateUrl: './logout.component.html',\n  styleUrls: ['./logout.component.css']\n})\nexport class LogoutComponent implements OnInit {\n  public message = new BehaviorSubject<string>(null);\n\n  constructor(\n    private authorizeService: AuthorizeService,\n    private activatedRoute: ActivatedRoute,\n    private router: Router) { }\n\n  async ngOnInit() {\n    const action = this.activatedRoute.snapshot.url[1];\n    switch (action.path) {\n      case LogoutActions.Logout:\n        if (!!window.history.state.local) {\n          await this.logout(this.getReturnUrl());\n        } else {\n          // This prevents regular links to <app>/authentication/logout from triggering a logout\n          this.message.next('The logout was not initiated from within the page.');\n        }\n\n        break;\n      case LogoutActions.LogoutCallback:\n        await this.processLogoutCallback();\n        break;\n      case LogoutActions.LoggedOut:\n        this.message.next('You successfully logged out!');\n        break;\n      default:\n        throw new Error(`Invalid action '${action}'`);\n    }\n  }\n\n  private async logout(returnUrl: string): Promise<void> {\n    const state: INavigationState = { returnUrl };\n    const isauthenticated = await this.authorizeService.isAuthenticated().pipe(\n      take(1)\n    ).toPromise();\n    if (isauthenticated) {\n      const result = await this.authorizeService.signOut(state);\n      switch (result.status) {\n        case AuthenticationResultStatus.Redirect:\n          break;\n        case AuthenticationResultStatus.Success:\n          await this.navigateToReturnUrl(returnUrl);\n          break;\n        case AuthenticationResultStatus.Fail:\n          this.message.next(result.message);\n          break;\n        default:\n          throw new Error('Invalid authentication result status.');\n      }\n    } else {\n      this.message.next('You successfully logged out!');\n    }\n  }\n\n  private async processLogoutCallback(): Promise<void> {\n    const url = window.location.href;\n    const result = await this.authorizeService.completeSignOut(url);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        // There should not be any redirects as the only time completeAuthentication finishes\n        // is when we are doing a redirect sign in flow.\n        throw new Error('Should not redirect.');\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(this.getReturnUrl(result.state));\n        break;\n      case AuthenticationResultStatus.Fail:\n        this.message.next(result.message);\n        break;\n      default:\n        throw new Error('Invalid authentication result status.');\n    }\n  }\n\n  private async navigateToReturnUrl(returnUrl: string) {\n    await this.router.navigateByUrl(returnUrl, {\n      replaceUrl: true\n    });\n  }\n\n  private getReturnUrl(state?: INavigationState): string {\n    const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl;\n    // If the url is comming from the query string, check that is either\n    // a relative url or an absolute url\n    if (fromQuery &&\n      !(fromQuery.startsWith(`${window.location.origin}/`) ||\n        /\\/[^\\/].*/.test(fromQuery))) {\n      // This is an extra check to prevent open redirects.\n      throw new Error('Invalid return url. The return url needs to have the same origin as the current page.');\n    }\n    return (state && state.returnUrl) ||\n      fromQuery ||\n      ApplicationPaths.LoggedOut;\n  }\n}\n\ninterface INavigationState {\n  [ReturnUrlType]: string;\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/angular-material.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatPaginatorModule } from '@angular/material/paginator';\nimport { MatSortModule } from '@angular/material/sort';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\n\n@NgModule({\n  imports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ],\n  exports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ]\n})\n\nexport class AngularMaterialModule { }\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/app.component.html",
    "content": "<body>\n\n  <div class=\"alert alert-warning\" *ngIf=\"!isConnected\">\n    <strong>WARNING</strong>: the app is currently <i>offline</i>:\n    some features that rely upon the back-end might not work as expected.\n    This message will automatically disappear\n    as soon as the internet connection becomes available again.\n  </div>\n\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { ConnectionService } from '../ng-connection-service/connection-service.service';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n\n  hasNetworkConnection: boolean;\n  hasInternetAccess: boolean;\n  isConnected: boolean;\n  status: string;\n\n  constructor(private connectionService: ConnectionService) {\n    this.connectionService.updateOptions({\n      heartbeatUrl: \"/isOnline.txt\"\n    });\n    this.connectionService.monitor().subscribe(currentState => {\n      this.hasNetworkConnection = currentState.hasNetworkConnection;\n      this.hasInternetAccess = currentState.hasInternetAccess;\n      if (this.hasNetworkConnection && this.hasInternetAccess) {\n        this.isConnected = true;\n        this.status = 'ONLINE';\n      }\n      else {\n        this.isConnected = false;\n        this.status = 'OFFLINE';\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { BaseFormComponent } from './base.form.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { CitiesComponent } from './cities/cities.component';\nimport { CityEditComponent } from './cities/city-edit.component';\nimport { CountriesComponent } from './countries/countries.component';\nimport { CountryEditComponent } from './countries/country-edit.component';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { AngularMaterialModule } from './angular-material.module';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\n\nimport { ApiAuthorizationModule } from 'src/api-authorization/api-authorization.module';\nimport { AuthorizeGuard } from 'src/api-authorization/authorize.guard';\nimport { AuthorizeInterceptor } from 'src/api-authorization/authorize.interceptor';\n\nimport { ServiceWorkerModule } from '@angular/service-worker';\nimport { environment } from '../environments/environment';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    BaseFormComponent,\n    NavMenuComponent,\n    HomeComponent,\n    CitiesComponent,\n    CityEditComponent,\n    CountriesComponent,\n    CountryEditComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    ApiAuthorizationModule,\n    RouterModule.forRoot([\n      {\n        path: '',\n        component: HomeComponent,\n        pathMatch: 'full'\n      },\n      {\n        path: 'cities',\n        component: CitiesComponent\n      },\n      {\n        path: 'city/:id',\n        component: CityEditComponent,\n        canActivate: [AuthorizeGuard]\n      },\n      {\n        path: 'city',\n        component: CityEditComponent,\n        canActivate: [AuthorizeGuard]\n      },\n      {\n        path: 'countries',\n        component: CountriesComponent\n      },\n      {\n        path: 'country/:id',\n        component: CountryEditComponent,\n        canActivate: [AuthorizeGuard]\n      },\n      {\n        path: 'country',\n        component: CountryEditComponent,\n        canActivate: [AuthorizeGuard]\n      }\n    ]),\n    BrowserAnimationsModule,\n    AngularMaterialModule,\n    ReactiveFormsModule,\n    ServiceWorkerModule.register(\n      'ngsw-worker.js',\n      {\n        registrationStrategy: 'registerImmediately'\n      })\n    ],\n  providers: [\n    {\n      provide: HTTP_INTERCEPTORS,\n      useClass: AuthorizeInterceptor,\n      multi: true\n    }\n  ],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/base.form.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { FormGroup } from '@angular/forms';\n\n@Component({\n    template: ''\n})  \nexport class BaseFormComponent {\n\n    // the form model\n    form: FormGroup;\n\n    constructor() {\n    }\n\n    // retrieve a FormControl\n    getControl(name: string) {\n        return this.form.get(name);\n    }\n\n    // returns TRUE if the FormControl is valid\n    isValid(name: string) {\n        var e = this.getControl(name);\n        return e && e.valid;\n    }\n\n    // returns TRUE if the FormControl has been changed\n    isChanged(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched);\n    }\n\n    // returns TRUE if the FormControl is raising an error,\n    // i.e. an invalid state after user changes\n    hasError(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched) && e.invalid;\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/base.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Observable } from 'rxjs';\n\n@Injectable()\nexport abstract class BaseService {\n    constructor(\n        public http: HttpClient,\n        public baseUrl: string\n    ) {\n    }\n\n    abstract getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string): Observable<ApiResult>;\n\n    abstract get<T>(id: number): Observable<T>;\n    abstract put<T>(item: T): Observable<T>;\n    abstract post<T>(item: T): Observable<T>;\n}\n\nexport interface ApiResult<T> {\n    data: T[];\n    pageIndex: number;\n    pageSize: number;\n    totalCount: number;\n    totalPages: number;\n    sortColumn: string;\n    sortOrder: string;\n    filterColumn: string;\n    filterQuery: string;\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/cities/cities.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/cities/cities.component.html",
    "content": "<h1>Cities</h1>\n\n<p>Here's a list of cities: feel free to play with it.</p>\n\n<p *ngIf=\"!cities\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"cities\">\n  <button type=\"button\"\n          [routerLink]=\"['/city']\"\n          class=\"btn btn-success\">\n      Add a new City\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!cities\">\n    <input matInput (keyup)=\"loadData($event.target.value)\"\n        placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"cities\" class=\"mat-elevation-z8\" [hidden]=\"!cities\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/city', city.id]\">{{city.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"lat\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Latitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lat}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"lon\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Longitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lon}} </td>\n  </ng-container>\n\n  <!-- CountryName Column -->\n  <ng-container matColumnDef=\"countryName\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Country</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/country', city.countryId]\">{{city.countryName}}</a>\n    </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!cities\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/cities/cities.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { RouterTestingModule } from '@angular/router/testing';\nimport { AngularMaterialModule } from '../angular-material.module';\nimport { of } from 'rxjs';\n\nimport { CitiesComponent } from './cities.component';\nimport { City } from './city';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\ndescribe('CitiesComponent', () => {\n  let fixture: ComponentFixture<CitiesComponent>;\n  let component: CitiesComponent;\n\n  // async beforeEach(): TestBed initialization\n  beforeEach(async(() => {\n\n    // Create a mock cityService object with a mock 'getData' method\n    let cityService = jasmine.createSpyObj<CityService>(\n      'CityService', ['getData']\n    );\n\n    // Configure the 'getData' spy method\n    cityService.getData.and.returnValue(\n      // return an Observable with some test data\n      of<ApiResult<City>>(<ApiResult<City>>{\n        data: [\n          <City>{\n            name: 'TestCity1',\n            id: 1, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          },\n          <City>{\n            name: 'TestCity2',\n            id: 2, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          },\n          <City>{\n            name: 'TestCity3',\n            id: 3, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          }\n        ],\n        totalCount: 3,\n        pageIndex: 0,\n        pageSize: 10\n      }));\n\n    TestBed.configureTestingModule({\n      declarations: [CitiesComponent],\n      imports: [\n        BrowserAnimationsModule,\n        AngularMaterialModule,\n        RouterTestingModule\n      ],\n      providers: [\n        {\n          provide: CityService,\n          useValue: cityService\n        }\n      ]\n    })\n      .compileComponents();\n  }));\n\n  // synchronous beforeEach(): fixtures and components setup\n  beforeEach(() => {\n    fixture = TestBed.createComponent(CitiesComponent);\n    component = fixture.componentInstance;\n\n    component.paginator = jasmine.createSpyObj(\n      \"MatPaginator\", [\"length\", \"pageIndex\", \"pageSize\"]\n    );\n\n    fixture.detectChanges();\n  });\n\n  it('should display a \"Cities\" title', async(() => {\n    let title = fixture.nativeElement\n      .querySelector('h1');\n    expect(title.textContent).toEqual('Cities');\n  }));\n\n  it('should contain a table with a list of one or more cities', async(() => {\n    let table = fixture.nativeElement\n      .querySelector('table.mat-table');\n    let tableRows = table\n      .querySelectorAll('tr.mat-row');\n    expect(tableRows.length).toBeGreaterThan(0);\n  }));\n});\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/cities/cities.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { City } from './city';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-cities',\n  templateUrl: './cities.component.html',\n  styleUrls: ['./cities.component.css']\n})\nexport class CitiesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'lat', 'lon', 'countryName'];\n  public cities: MatTableDataSource<City>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private cityService: CityService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n    var sortColumn = (this.sort)\n      ? this.sort.active\n      : this.defaultSortColumn;\n\n    var sortOrder = (this.sort)\n      ? this.sort.direction\n      : this.defaultSortOrder;\n\n    var filterColumn = (this.filterQuery)\n      ? this.defaultFilterColumn\n      : null;\n\n    var filterQuery = (this.filterQuery)\n      ? this.filterQuery\n      : null;\n\n    this.cityService.getData<ApiResult<City>>(\n      event.pageIndex,\n      event.pageSize,\n      sortColumn,\n      sortOrder,\n      filterColumn,\n      filterQuery)\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.cities = new MatTableDataSource<City>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/cities/city-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/cities/city-edit.component.html",
    "content": "<div class=\"city-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !city\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n\n        <div *ngIf=\"form.invalid && form.errors?.isDupeCity\"\n             class=\"alert alert-danger\">\n              <strong>ERROR</strong>:\n              A city with the same <i>name</i>, <i>lat</i>,\n              <i>lon</i> and <i>country</i> already exists.\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"name\">City name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"City name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"hasError('name')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"lat\">City latitude:</label>\n            <br />\n            <input type=\"text\" id=\"lat\"\n                   formControlName=\"lat\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lat')\"\n                  class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lat').errors?.required\">\n                  Latitude is required.\n                </div>\n                <div *ngIf=\"form.get('lat').errors?.pattern\">\n                  Latitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n\n\n        <div class=\"form-group\">\n            <label for=\"lon\">City longitude:</label>\n            <br />\n            <input type=\"text\" id=\"lon\"\n                   formControlName=\"lon\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lon')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lon').errors?.required\">\n                  Longitude is required.\n                </div>\n                <div *ngIf=\"form.get('lon').errors?.pattern\">\n                  Longitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"countryId\">Country:</label>\n            <br />\n            <mat-form-field *ngIf=\"countries\">\n              <mat-label>Select a Country...</mat-label>\n              <mat-select id=\"countryId\" formControlName=\"countryId\">\n                <mat-option *ngFor=\"let country of countries\" [value]=\"country.id\">\n                  {{country.name}}\n                </mat-option>\n              </mat-select>\n            </mat-form-field>\n\n            <div *ngIf=\"hasError('countryId')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('countryId').errors?.required\">\n                  Please select a Country.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/cities']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n\n<!-- Form debug info panel -->\n<div class=\"card bg-light mb-3\">\n  <div class=\"card-header\">Form Debug Info</div>\n  <div class=\"card-body\">\n    <div class=\"card-text\">\n      <div><strong>Form value:</strong></div>\n      <div class=\"help-block\">\n          {{ form.value | json }}\n      </div>\n      <div class=\"mt-2\"><strong>Form status:</strong></div>\n      <div class=\"help-block\">\n          {{ form.status | json }}\n      </div>\n    </div>\n  </div>\n</div>\n\n<!-- Form activity log panel -->\n<div class=\"card bg-light mb-3\">\n  <div class=\"card-header\">Form Activity Log</div>\n  <div class=\"card-body\">\n    <div class=\"card-text\">\n      <div class=\"help-block\">\n        <span *ngIf=\"activityLog\" \n            [innerHTML]=\"activityLog\"></span>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/cities/city-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormControl, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { City } from './city';\nimport { Country } from '../countries/country';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-city-edit',\n  templateUrl: './city-edit.component.html',\n  styleUrls: ['./city-edit.component.css']\n})\nexport class CityEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  city: City;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new city,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  // the countries array for the select\n  countries: Country[];\n\n  // Activity Log (for debugging purposes)\n  activityLog: string = '';\n\n  constructor(\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private cityService: CityService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = new FormGroup({\n      name: new FormControl('', Validators.required),\n      lat: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      lon: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      countryId: new FormControl('', Validators.required)\n    }, null, this.isDupeCity());\n\n    // react to form changes\n    this.form.valueChanges\n      .subscribe(val => {\n        if (!this.form.dirty) {\n          this.log(\"Form Model has been loaded.\");\n        }\n        else {\n          this.log(\"Form was updated by the user.\");\n        }\n      });\n\n    // react to changes in the form.name control\n    this.form.get(\"name\")!.valueChanges\n      .subscribe(val => {\n        if (!this.form.dirty) {\n          this.log(\"Name has been loaded with initial values.\");\n        }\n        else {\n          this.log(\"Name was updated by the user.\");\n        }\n      });\n\n    this.loadData();\n  }\n\n  log(str: string) {\n    this.activityLog += \"[\"\n      + new Date().toLocaleString()\n      + \"] \" + str + \"<br />\";\n  }\n\n  loadData() {\n\n    // load countries\n    this.loadCountries();\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the city from the server\n      this.cityService.get<City>(this.id).subscribe(result => {\n        this.city = result;\n        this.title = \"Edit - \" + this.city.name;\n\n        // update the form with the city value\n        this.form.patchValue(this.city);\n      }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new City\";\n    }\n  }\n\n  loadCountries() {\n    // fetch all the countries from the server\n    this.cityService.getCountries<ApiResult<Country>>(\n      0,\n      9999,\n      \"name\",\n      null,\n      null,\n      null,\n    ).subscribe(result => {\n      this.countries = result.data;\n    }, error => console.error(error));\n  }\n\n  onSubmit() {\n\n    var city = (this.id) ? this.city : <City>{};\n\n    city.name = this.form.get(\"name\").value;\n    city.lat = +this.form.get(\"lat\").value;\n    city.lon = +this.form.get(\"lon\").value;\n    city.countryId = +this.form.get(\"countryId\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.cityService\n        .put<City>(city)\n        .subscribe(result => {\n\n          console.log(\"City \" + city.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.cityService\n        .post<City>(city)\n        .subscribe(result => {\n\n          console.log(\"City \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeCity(): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n      var city = <City>{};\n      city.id = (this.id) ? this.id : 0;\n      city.name = this.form.get(\"name\").value;\n      city.lat = +this.form.get(\"lat\").value;\n      city.lon = +this.form.get(\"lon\").value;\n      city.countryId = +this.form.get(\"countryId\").value;\n\n      return this.cityService.isDupeCity(city)\n        .pipe(map(result => {\n          return (result ? { isDupeCity: true } : null);\n        }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/cities/city.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CityService\n    extends BaseService {\n    constructor(\n        http: HttpClient,\n        @Inject('BASE_URL') baseUrl: string) {\n        super(http, baseUrl);\n    }\n\n    getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Cities';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    get<City>(id): Observable<City> {\n        var url = this.baseUrl + \"api/Cities/\" + id;\n        return this.http.get<City>(url);\n    }\n\n    put<City>(item): Observable<City> {\n        var url = this.baseUrl + \"api/Cities/\" + item.id;\n        return this.http.put<City>(url, item);\n    }\n\n    post<City>(item): Observable<City> {\n        var url = this.baseUrl + \"api/Cities\";\n        return this.http.post<City>(url, item);\n    }\n\n    getCountries<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Countries';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    isDupeCity(item): Observable<boolean> {\n        var url = this.baseUrl + \"api/Cities/IsDupeCity\";\n        return this.http.post<boolean>(url, item);\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/cities/city.ts",
    "content": "export interface City {\n    id: number;\n    name: string;\n    lat: number;\n    lon: number;\n    countryId: number;\n    countryName: string;\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/countries/countries.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/countries/countries.component.html",
    "content": "<h1>Countries</h1>\n\n<p>Here's a list of countries: feel free to play with it.</p>\n\n<p *ngIf=\"!countries\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"countries\">\n  <button type=\"button\"\n          [routerLink]=\"['/country']\"\n          class=\"btn btn-success\">\n      Add a new Country\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!countries\">\n  <input matInput (keyup)=\"loadData($event.target.value)\"\n      placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"countries\" class=\"mat-elevation-z8\" [hidden]=\"!countries\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let country\">\n      <a [routerLink]=\"['/country', country.id]\">{{country.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"iso2\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 2</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso2}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"iso3\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 3</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso3}} </td>\n  </ng-container>\n\n  <!-- TotCities Column -->\n  <ng-container matColumnDef=\"totCities\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Tot. Cities</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.totCities}} </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!countries\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/countries/countries.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { Country } from './country';\nimport { CountryService } from './country.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-countries',\n  templateUrl: './countries.component.html',\n  styleUrls: ['./countries.component.css']\n})\nexport class CountriesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'iso2', 'iso3', 'totCities'];\n  public countries: MatTableDataSource<Country>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private countryService: CountryService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n    var sortColumn = (this.sort)\n      ? this.sort.active\n      : this.defaultSortColumn;\n\n    var sortOrder = (this.sort)\n      ? this.sort.direction\n      : this.defaultSortOrder;\n\n    var filterColumn = (this.filterQuery)\n      ? this.defaultFilterColumn\n      : null;\n\n    var filterQuery = (this.filterQuery)\n      ? this.filterQuery\n      : null;\n\n    this.countryService.getData<ApiResult<Country>>(\n      event.pageIndex,\n      event.pageSize,\n      sortColumn,\n      sortOrder,\n      filterColumn,\n      filterQuery)\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.countries = new MatTableDataSource<Country>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/countries/country-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/countries/country-edit.component.html",
    "content": "<div class=\"country-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !country\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n     \n        <div class=\"form-group\">\n            <label for=\"name\">Country name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"Country name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"form.get('name').invalid &&\n                 (form.get('name').dirty || form.get('name').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n                <div *ngIf=\"form.get('name').errors?.isDupeField\">\n                  Name does already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"iso2\">ISO 3166-1 ALPHA-2 Country Code (2 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso2\"\n                   formControlName=\"iso2\" required\n                   placeholder=\"2 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso2').invalid &&\n                 (form.get('iso2').dirty || form.get('iso2').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso2').errors?.required\">\n                  ISO 3166-1 ALPHA-2 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.pattern\">\n                  ISO 3166-1 ALPHA-2 country code requires 2 letters.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-2 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n              <div class=\"form-group\">\n            <label for=\"iso3\">ISO 3166-1 ALPHA-3 Country Code (3 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso3\"\n                   formControlName=\"iso3\" required\n                   placeholder=\"3 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso3').invalid &&\n                 (form.get('iso3').dirty || form.get('iso3').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso3').errors?.required\">\n                  ISO 3166-1 ALPHA-3 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.pattern\">\n                  ISO 3166-1 ALPHA-3 country code requires 3 letters.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-3 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/countries']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/countries/country-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormBuilder, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { map } from 'rxjs/operators';\nimport { Observable } from 'rxjs';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { Country } from '../countries/country';\nimport { CountryService } from './country.service';\n\n@Component({\n  selector: 'app-country-edit',\n  templateUrl: './country-edit.component.html',\n  styleUrls: ['./country-edit.component.css']\n})\nexport class CountryEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  country: Country;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new country,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  constructor(\n    private fb: FormBuilder,\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private countryService: CountryService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = this.fb.group({\n      name: ['',\n        Validators.required,\n        this.isDupeField(\"name\")\n      ],\n      iso2: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{2}/)\n        ],\n        this.isDupeField(\"iso2\")\n      ],\n      iso3: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{3}/)\n        ],\n        this.isDupeField(\"iso3\")\n      ]\n    });\n\n    this.loadData();\n  }\n\n  loadData() {\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the country from the server\n      this.countryService.get<Country>(this.id)\n        .subscribe(result => {\n          this.country = result;\n          this.title = \"Edit - \" + this.country.name;\n\n          // update the form with the country value\n          this.form.patchValue(this.country);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new Country\";\n    }\n  }\n\n  onSubmit() {\n\n    var country = (this.id) ? this.country : <Country>{};\n\n    country.name = this.form.get(\"name\").value;\n    country.iso2 = this.form.get(\"iso2\").value;\n    country.iso3 = this.form.get(\"iso3\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.countryService\n        .put<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + country.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.countryService\n        .post<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeField(fieldName: string): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n\n      var countryId = (this.id) ? this.id.toString() : \"0\";\n\n      return this.countryService.isDupeField(\n        countryId,\n        fieldName,\n        control.value)\n        .pipe(map(result => {\n          return (result ? { isDupeField: true } : null);\n        }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/countries/country.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CountryService\n    extends BaseService {\n    constructor(\n        http: HttpClient,\n        @Inject('BASE_URL') baseUrl: string) {\n        super(http, baseUrl);\n    }\n\n    getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Countries';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    get<Country>(id): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries/\" + id;\n        return this.http.get<Country>(url);\n    }\n\n    put<Country>(item): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries/\" + item.id;\n        return this.http.put<Country>(url, item);\n    }\n\n    post<Country>(item): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries\";\n        return this.http.post<Country>(url, item);\n    }\n\n    isDupeField(countryId, fieldName, fieldValue): Observable<boolean> {\n        var params = new HttpParams()\n            .set(\"countryId\", countryId)\n            .set(\"fieldName\", fieldName)\n            .set(\"fieldValue\", fieldValue);\n        var url = this.baseUrl + \"api/Countries/IsDupeField\";\n        return this.http.post<boolean>(url, null, { params });\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/countries/country.ts",
    "content": "export interface Country {\n    id: number;\n    name: string;\n    iso2: string;\n    iso3: string;\n    totCities: number;\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">WorldCities</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <app-login-menu></app-login-menu>\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/cities']\"\n              >Cities</a\n            >\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/countries']\"\n              >Countries</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\"/>\n    <title>WorldCities</title>\n    <base href=\"/\"/>\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\"/>\n      <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&amp;display=swap\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n  <link rel=\"manifest\" href=\"manifest.webmanifest\">\n  <meta name=\"theme-color\" content=\"#1976d2\">\n</head>\n  <body>\n    <app-root>Loading...</app-root>\n    <noscript>Please enable JavaScript to continue using this application.</noscript>\n</body>\n</html>\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/main.ts",
    "content": "import 'hammerjs';\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/manifest.webmanifest",
    "content": "{\n  \"name\": \"WorldCities\",\n  \"short_name\": \"WorldCities\",\n  \"theme_color\": \"#1976d2\",\n  \"background_color\": \"#fafafa\",\n  \"display\": \"standalone\",\n  \"scope\": \"/\",\n  \"start_url\": \"/\",\n  \"icons\": [\n    {\n      \"src\": \"assets/icons/icon-72x72.png\",\n      \"sizes\": \"72x72\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-96x96.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-128x128.png\",\n      \"sizes\": \"128x128\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-144x144.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-152x152.png\",\n      \"sizes\": \"152x152\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-384x384.png\",\n      \"sizes\": \"384x384\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ]\n}"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/ng-connection-service/connection-service.module.ts",
    "content": "import {NgModule} from '@angular/core';\nimport {ConnectionService} from './connection-service.service';\nimport {HttpClientModule} from '@angular/common/http';\n\n@NgModule({\n  imports: [HttpClientModule],\n  providers: [ConnectionService]\n})\nexport class ConnectionServiceModule {\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/ng-connection-service/connection-service.service.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { ConnectionService } from './connection-service.service';\n\ndescribe('ConnectionServiceService', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [ConnectionService]\n    });\n  });\n\n  it('should be created', inject([ConnectionService], (service: ConnectionService) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/ng-connection-service/connection-service.service.ts",
    "content": "import {EventEmitter, Inject, Injectable, InjectionToken, OnDestroy, Optional} from '@angular/core';\nimport {fromEvent, Observable, Subscription, timer} from 'rxjs';\nimport {debounceTime, delay, retryWhen, startWith, switchMap, tap} from 'rxjs/operators';\nimport {HttpClient} from '@angular/common/http';\nimport * as _ from 'lodash';\n\n/**\n * Instance of this interface is used to report current connection status.\n */\nexport interface ConnectionState {\n  /**\n   * \"True\" if browser has network connection. Determined by Window objects \"online\" / \"offline\" events.\n   */\n  hasNetworkConnection: boolean;\n  /**\n   * \"True\" if browser has Internet access. Determined by heartbeat system which periodically makes request to heartbeat Url.\n   */\n  hasInternetAccess: boolean;\n}\n\n/**\n * Instance of this interface could be used to configure \"ConnectionService\".\n */\nexport interface ConnectionServiceOptions {\n  /**\n   * Controls the Internet connectivity heartbeat system. Default value is 'true'.\n   */\n  enableHeartbeat?: boolean;\n  /**\n   * Url used for checking Internet connectivity, heartbeat system periodically makes \"HEAD\" requests to this URL to determine Internet\n   * connection status. Default value is \"//internethealthtest.org\".\n   */\n  heartbeatUrl?: string;\n  /**\n   * Interval used to check Internet connectivity specified in milliseconds. Default value is \"30000\".\n   */\n  heartbeatInterval?: number;\n  /**\n   * Interval used to retry Internet connectivity checks when an error is detected (when no Internet connection). Default value is \"1000\".\n   */\n  heartbeatRetryInterval?: number;\n  /**\n   * HTTP method used for requesting heartbeat Url. Default is 'head'.\n   */\n  requestMethod?: 'get' | 'post' | 'head' | 'options';\n\n}\n\n/**\n * InjectionToken for specifing ConnectionService options.\n */\nexport const ConnectionServiceOptionsToken: InjectionToken<ConnectionServiceOptions> = new InjectionToken('ConnectionServiceOptionsToken');\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class ConnectionService implements OnDestroy {\n  private static DEFAULT_OPTIONS: ConnectionServiceOptions = {\n    enableHeartbeat: true,\n    heartbeatUrl: '//internethealthtest.org',\n    heartbeatInterval: 30000,\n    heartbeatRetryInterval: 1000,\n    requestMethod: 'head'\n  };\n\n  private stateChangeEventEmitter = new EventEmitter<ConnectionState>();\n\n  private currentState: ConnectionState = {\n    hasInternetAccess: false,\n    hasNetworkConnection: window.navigator.onLine\n  };\n  private offlineSubscription: Subscription;\n  private onlineSubscription: Subscription;\n  private httpSubscription: Subscription;\n  private serviceOptions: ConnectionServiceOptions;\n\n  /**\n   * Current ConnectionService options. Notice that changing values of the returned object has not effect on service execution.\n   * You should use \"updateOptions\" function.\n   */\n  get options(): ConnectionServiceOptions {\n    return _.clone(this.serviceOptions);\n  }\n\n  constructor(private http: HttpClient, @Inject(ConnectionServiceOptionsToken) @Optional() options: ConnectionServiceOptions) {\n    this.serviceOptions = _.defaults({}, options, ConnectionService.DEFAULT_OPTIONS);\n\n    this.checkNetworkState();\n    this.checkInternetState();\n  }\n\n  private checkInternetState() {\n\n    if (!_.isNil(this.httpSubscription)) {\n      this.httpSubscription.unsubscribe();\n    }\n\n    if (this.serviceOptions.enableHeartbeat) {\n      this.httpSubscription = timer(0, this.serviceOptions.heartbeatInterval)\n        .pipe(\n          switchMap(() => this.http[this.serviceOptions.requestMethod](this.serviceOptions.heartbeatUrl, {responseType: 'text'})),\n          retryWhen(errors =>\n            errors.pipe(\n              // log error message\n              tap(val => {\n                console.error('Http error:', val);\n                this.currentState.hasInternetAccess = false;\n                this.emitEvent();\n              }),\n              // restart after 5 seconds\n              delay(this.serviceOptions.heartbeatRetryInterval)\n            )\n          )\n        )\n        .subscribe(result => {\n          this.currentState.hasInternetAccess = true;\n          this.emitEvent();\n        });\n    } else {\n      this.currentState.hasInternetAccess = false;\n      this.emitEvent();\n    }\n  }\n\n  private checkNetworkState() {\n    this.onlineSubscription = fromEvent(window, 'online').subscribe(() => {\n      this.currentState.hasNetworkConnection = true;\n      this.checkInternetState();\n      this.emitEvent();\n    });\n\n    this.offlineSubscription = fromEvent(window, 'offline').subscribe(() => {\n      this.currentState.hasNetworkConnection = false;\n      this.checkInternetState();\n      this.emitEvent();\n    });\n  }\n\n  private emitEvent() {\n    this.stateChangeEventEmitter.emit(this.currentState);\n  }\n\n  ngOnDestroy(): void {\n    try {\n      this.offlineSubscription.unsubscribe();\n      this.onlineSubscription.unsubscribe();\n      this.httpSubscription.unsubscribe();\n    } catch (e) {\n    }\n  }\n\n  /**\n   * Monitor Network & Internet connection status by subscribing to this observer. If you set \"reportCurrentState\" to \"false\" then\n   * function will not report current status of the connections when initially subscribed.\n   * @param reportCurrentState Report current state when initial subscription. Default is \"true\"\n   */\n  monitor(reportCurrentState = true): Observable<ConnectionState> {\n    return reportCurrentState ?\n      this.stateChangeEventEmitter.pipe(\n        debounceTime(300),\n        startWith(this.currentState),\n      )\n      :\n      this.stateChangeEventEmitter.pipe(\n        debounceTime(300)\n      );\n  }\n\n  /**\n   * Update options of the service. You could specify partial options object. Values that are not specified will use default / previous\n   * option values.\n   * @param options Partial option values.\n   */\n  updateOptions(options: Partial<ConnectionServiceOptions>) {\n    this.serviceOptions = _.defaults({}, options, this.serviceOptions);\n    this.checkInternetState();\n  }\n\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_11/WorldCities/Controllers/CitiesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CitiesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CitiesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Cities/?pageIndex=0&pageSize=10\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CityDTO>>> GetCities(\n                int pageIndex = 0,\n                int pageSize = 10,\n                string sortColumn = null,\n                string sortOrder = null,\n                string filterColumn = null,\n                string filterQuery = null)\n        {\n            return await ApiResult<CityDTO>.CreateAsync(\n                    _context.Cities\n                        .Select(c => new CityDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            Lat = c.Lat,\n                            Lon = c.Lon,\n                            CountryId = c.Country.Id,\n                            CountryName = c.Country.Name\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        // GET: api/Cities/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<City>> GetCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            return city;\n        }\n\n        // PUT: api/Cities/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCity(int id, City city)\n        {\n            if (id != city.Id)\n            {\n                return BadRequest();\n            }\n\n            //var sourceCity = _context.Cities.Where(i => i.Id == city.Id).FirstOrDefault();\n            //if (sourceCity == null) return BadRequest();\n            //sourceCity.Name = city.Name;\n            //sourceCity.Lat = city.Lat;\n            //sourceCity.Lon = city.Lon;\n\n            _context.Entry(city).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CityExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Cities\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPost]\n        public async Task<ActionResult<City>> PostCity(City city)\n        {\n            _context.Cities.Add(city);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCity\", new { id = city.Id }, city);\n        }\n\n        // DELETE: api/Cities/5\n        [Authorize]\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<City>> DeleteCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            _context.Cities.Remove(city);\n            await _context.SaveChangesAsync();\n\n            return city;\n        }\n\n        private bool CityExists(int id)\n        {\n            return _context.Cities.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeCity\")]\n        public bool IsDupeCity(City city)\n        {\n            return _context.Cities.Any(\n                e => e.Name == city.Name\n                && e.Lat == city.Lat\n                && e.Lon == city.Lon\n                && e.CountryId == city.CountryId\n                && e.Id != city.Id);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Controllers/CountriesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Dynamic.Core;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CountriesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CountriesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Countries/?pageIndex=0&pageSize=10\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CountryDTO>>> GetCountries(\n        int pageIndex = 0,\n        int pageSize = 10,\n        string sortColumn = null,\n        string sortOrder = null,\n        string filterColumn = null,\n        string filterQuery = null)\n        {\n            return await ApiResult<CountryDTO>.CreateAsync(\n                    _context.Countries\n                        .Select(c => new CountryDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            ISO2 = c.ISO2,\n                            ISO3 = c.ISO3,\n                            TotCities = c.Cities.Count\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        //public async Task<ActionResult<ApiResult<dynamic>>> GetCountries(\n        //    int pageIndex = 0,\n        //    int pageSize = 10,\n        //    string sortColumn = null,\n        //    string sortOrder = null,\n        //    string filterColumn = null,\n        //    string filterQuery = null)\n        //{\n        //    return await ApiResult<dynamic>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n\n        //public async Task<ActionResult<ApiResult<Country>>> GetCountries(\n        //int pageIndex = 0,\n        //int pageSize = 10,\n        //string sortColumn = null,\n        //string sortOrder = null,\n        //string filterColumn = null,\n        //string filterQuery = null)\n        //{\n        //    return await ApiResult<Country>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new Country()\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n        // GET: api/Countries/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<Country>> GetCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            return country;\n        }\n\n        // PUT: api/Countries/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCountry(int id, Country country)\n        {\n            if (id != country.Id)\n            {\n                return BadRequest();\n            }\n\n            _context.Entry(country).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CountryExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Countries\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPost]\n        public async Task<ActionResult<Country>> PostCountry(Country country)\n        {\n            _context.Countries.Add(country);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCountry\", new { id = country.Id }, country);\n        }\n\n        // DELETE: api/Countries/5\n        [Authorize]\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<Country>> DeleteCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            _context.Countries.Remove(country);\n            await _context.SaveChangesAsync();\n\n            return country;\n        }\n\n        private bool CountryExists(int id)\n        {\n            return _context.Countries.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeField\")]\n        public bool IsDupeField(\n            int countryId, \n            string fieldName, \n            string fieldValue)\n        {\n            // Standard approach(using strongly-typed LAMBA expressions)\n            //switch (fieldName)\n            //{\n            //    case \"name\":\n            //        return _context.Countries.Any(\n            //            c => c.Name == fieldValue && c.Id != countryId);\n            //    case \"iso2\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO2 == fieldValue && c.Id != countryId);\n            //    case \"iso3\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO3 == fieldValue && c.Id != countryId);\n            //    default:\n            //        return false;\n            //}\n\n            // Dynamic approach (using System.Linq.Dynamic.Core)\n            return (ApiResult<Country>.IsValidProperty(fieldName, true))\n                ? _context.Countries.Any(\n                    String.Format(\"{0} == @0 && Id != @1\", fieldName),\n                    fieldValue,\n                    countryId)\n                : false;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Controllers/OidcConfigurationController.cs",
    "content": "﻿using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Controllers\n{\n    public class OidcConfigurationController : Controller\n    {\n        private readonly ILogger<OidcConfigurationController> logger;\n\n        public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger<OidcConfigurationController> _logger)\n        {\n            ClientRequestParametersProvider = clientRequestParametersProvider;\n            logger = _logger;\n        }\n\n        public IClientRequestParametersProvider ClientRequestParametersProvider { get; }\n\n        [HttpGet(\"_configuration/{clientId}\")]\n        public IActionResult GetClientRequestParameters([FromRoute]string clientId)\n        {\n            var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);\n            return Ok(parameters);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Controllers/SeedController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing WorldCities.Data;\nusing OfficeOpenXml;\nusing System.IO;\nusing Microsoft.AspNetCore.Hosting;\nusing WorldCities.Data.Models;\nusing System.Text.Json;\nusing Microsoft.AspNetCore.Identity;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]/[action]\")]\n    [ApiController]\n    public class SeedController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n        private readonly RoleManager<IdentityRole> _roleManager;\n        private readonly UserManager<ApplicationUser> _userManager;\n        private readonly IWebHostEnvironment _env;\n\n        public SeedController(\n            ApplicationDbContext context,\n            RoleManager<IdentityRole> roleManager,\n            UserManager<ApplicationUser> userManager,\n            IWebHostEnvironment env)\n        {\n            _context = context;\n            _roleManager = roleManager;\n            _userManager = userManager;\n            _env = env;\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> Import()\n        {\n            // NOTE: This method has been updated on 2020.09.13.\n            // The new version is more efficient than the code described in the book's Chapter 4.\n            // ref.: https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/issues/15\n\n            var path = Path.Combine(\n                _env.ContentRootPath,\n                String.Format(\"Data/Source/worldcities.xlsx\"));\n\n            using (var stream = new FileStream(\n                path,\n                FileMode.Open,\n                FileAccess.Read))\n            {\n                using (var ep = new ExcelPackage(stream))\n                {\n                    // get the first worksheet\n\n                    var ws = ep.Workbook.Worksheets[0];\n\n                    // initialize the record counters\n                    var nCountries = 0;\n                    var nCities = 0;\n\n                    #region Import all Countries\n                    // create a list containing all the countries already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCountries = _context.Countries.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n                        var name = row[nRow, 5].GetValue<string>();\n\n                        // does this country already exist in the database?\n                        if (lstCountries.Where(c => c.Name == name).Count() == 0)\n                        {\n                            // create the Country entity and fill it with xlsx data\n                            var country = new Country();\n                            country.Name = name;\n                            country.ISO2 = row[nRow, 6].GetValue<string>();\n                            country.ISO3 = row[nRow, 7].GetValue<string>();\n\n                            // add the new country to the DB context\n                            _context.Countries.Add(country);\n\n                            // store the country to retrieve its Id later on\n                            lstCountries.Add(country);\n\n                            // increment the counter\n                            nCountries++;\n                        }\n                    }\n\n                    // save all the countries into the Database\n                    if (nCountries > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    #region Import all Cities\n                    // create a list containing all the cities already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCities = _context.Cities.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n\n                        var name = row[nRow, 1].GetValue<string>();\n                        var name_ASCII = row[nRow, 2].GetValue<string>();\n                        var countryName = row[nRow, 5].GetValue<string>();\n                        var lat = row[nRow, 3].GetValue<decimal>();\n                        var lon = row[nRow, 4].GetValue<decimal>();\n                        // retrieve country and countryId\n                        var country = lstCountries.Where(c => c.Name == countryName)\n                            .FirstOrDefault();\n                        var countryId = country.Id;\n\n                        // does this city already exist in the database?\n                        if (lstCities.Where(\n                            c => c.Name == name\n                            && c.Lat == lat\n                            && c.Lon == lon\n                            && c.CountryId == countryId\n                        ).Count() == 0)\n                        {\n                            // create the City entity and fill it with xlsx data\n                            var city = new City();\n                            city.Name = name;\n                            city.Name_ASCII = name_ASCII;\n                            city.Lat = lat;\n                            city.Lon = lon;\n                            city.CountryId = countryId;\n\n                            // add the new city to the DB context\n                            _context.Cities.Add(city);\n\n                            // increment the counter\n                            nCities++;\n                        }\n                    }\n\n                    // save all the cities into the Database\n                    if (nCities > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    return new JsonResult(new\n                    {\n                        Cities = nCities,\n                        Countries = nCountries\n                    });\n                }\n            }\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> CreateDefaultUsers()\n        {\n            // setup the default role names\n            string role_RegisteredUser = \"RegisteredUser\";\n            string role_Administrator = \"Administrator\";\n\n            // create the default roles (if they doesn't exist yet)\n            if (await _roleManager.FindByNameAsync(role_RegisteredUser) == null)\n                await _roleManager.CreateAsync(new IdentityRole(role_RegisteredUser));\n\n            if (await _roleManager.FindByNameAsync(role_Administrator) == null)\n                await _roleManager.CreateAsync(new IdentityRole(role_Administrator));\n\n            // create a list to track the newly added users\n            var addedUserList = new List<ApplicationUser>();\n\n            // check if the admin user already exist\n            var email_Admin = \"admin@email.com\";\n            if (await _userManager.FindByNameAsync(email_Admin) == null)\n            {\n                // create a new admin ApplicationUser account\n                var user_Admin = new ApplicationUser()\n                {\n                    SecurityStamp = Guid.NewGuid().ToString(),\n                    UserName = email_Admin,\n                    Email = email_Admin,\n                };\n\n                // insert the admin user into the DB\n                await _userManager.CreateAsync(user_Admin, \"MySecr3t$\");\n\n                // assign the \"RegisteredUser\" and \"Administrator\" roles\n                await _userManager.AddToRoleAsync(user_Admin, role_RegisteredUser);\n                await _userManager.AddToRoleAsync(user_Admin, role_Administrator);\n\n                // confirm the e-mail and remove lockout\n                user_Admin.EmailConfirmed = true;\n                user_Admin.LockoutEnabled = false;\n\n                // add the admin user to the added users list\n                addedUserList.Add(user_Admin);\n            }\n\n            // check if the standard user already exist\n            var email_User = \"user@email.com\";\n            if (await _userManager.FindByNameAsync(email_User) == null)\n            {\n                // create a new standard ApplicationUser account\n                var user_User = new ApplicationUser()\n                {\n                    SecurityStamp = Guid.NewGuid().ToString(),\n                    UserName = email_User,\n                    Email = email_User\n                };\n\n                // insert the standard user into the DB\n                await _userManager.CreateAsync(user_User, \"MySecr3t$\");\n\n                // assign the \"RegisteredUser\" role\n                await _userManager.AddToRoleAsync(user_User, role_RegisteredUser);\n\n                // confirm the e-mail and remove lockout\n                user_User.EmailConfirmed = true;\n                user_User.LockoutEnabled = false;\n\n                // add the standard user to the added users list\n                addedUserList.Add(user_User);\n            }\n\n            // if we added at least one user, persist the changes into the DB\n            if (addedUserList.Count > 0)\n                await _context.SaveChangesAsync();\n\n            return new JsonResult(new\n            {\n                Count = addedUserList.Count,\n                Users = addedUserList\n            });\n        }\n    }\n}"
  },
  {
    "path": "Chapter_11/WorldCities/Data/ApiResult.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Linq.Dynamic.Core;\nusing System.Reflection;\nusing System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class ApiResult<T>\n    {\n        /// <summary>\n        /// Private constructor called by the CreateAsync method.\n        /// </summary>\n        private ApiResult(\n            List<T> data,\n            int count,\n            int pageIndex,\n            int pageSize,\n            string sortColumn,\n            string sortOrder,\n            string filterColumn,\n            string filterQuery)\n        {\n            Data = data;\n            PageIndex = pageIndex;\n            PageSize = pageSize;\n            TotalCount = count;\n            TotalPages = (int)Math.Ceiling(count / (double)pageSize);\n            SortColumn = sortColumn;\n            SortOrder = sortOrder;\n            FilterColumn = filterColumn;\n            FilterQuery = filterQuery;\n        }\n\n        #region Methods\n        /// <summary>\n        /// Pages, sorts and/or filters a IQueryable source.\n        /// </summary>\n        /// <param name=\"source\">An IQueryable source of generic type</param>\n        /// <param name=\"pageIndex\">Zero-based current page index (0 = first page)</param>\n        /// <param name=\"pageSize\">The actual size of each page</param>\n        /// <param name=\"sortColumn\">The sorting colum name</param>\n        /// <param name=\"sortOrder\">The sorting order (\"ASC\" or \"DESC\")</param>\n        /// <param name=\"filterColumn\">The filtering column name</param>\n        /// <param name=\"filterQuery\">The filtering query (value to lookup)</param>\n        /// <returns>\n        /// A object containing the IQueryable paged/sorted/filtered result \n        /// and all the relevant paging/sorting/filtering navigation info.\n        /// </returns>\n        public static async Task<ApiResult<T>> CreateAsync(\n            IQueryable<T> source,\n            int pageIndex,\n            int pageSize,\n            string sortColumn = null,\n            string sortOrder = null,\n            string filterColumn = null,\n            string filterQuery = null)\n        {\n            if (!String.IsNullOrEmpty(filterColumn)\n                && !String.IsNullOrEmpty(filterQuery)\n                && IsValidProperty(filterColumn))\n            {\n                source = source.Where(\n                    String.Format(\"{0}.Contains(@0)\",\n                    filterColumn),\n                    filterQuery);\n            }\n\n            var count = await source.CountAsync();\n\n            if (!String.IsNullOrEmpty(sortColumn)\n                && IsValidProperty(sortColumn))\n            {\n                sortOrder = !String.IsNullOrEmpty(sortOrder)\n                    && sortOrder.ToUpper() == \"ASC\"\n                    ? \"ASC\"\n                    : \"DESC\";\n                source = source.OrderBy(\n                    String.Format(\n                        \"{0} {1}\",\n                        sortColumn,\n                        sortOrder)\n                    );\n            }\n\n            source = source\n                .Skip(pageIndex * pageSize)\n                .Take(pageSize);\n\n            // retrieve the SQL query (for debug purposes)\n            #if DEBUG\n            {\n                var sql = source.ToSql();\n                // do something with the sql string\n            }\n            #endif\n\n            var data = await source.ToListAsync();\n\n            return new ApiResult<T>(\n                data,\n                count,\n                pageIndex,\n                pageSize,\n                sortColumn,\n                sortOrder,\n                filterColumn,\n                filterQuery);\n        }\n\n        /// <summary>\n        /// Checks if the given property name exists\n        /// to protect against SQL injection attacks\n        /// </summary>\n        public static bool IsValidProperty(\n            string propertyName,\n            bool throwExceptionIfNotFound = true)\n        {\n            var prop = typeof(T).GetProperty(\n                propertyName,\n                BindingFlags.IgnoreCase |\n                BindingFlags.Public |\n                BindingFlags.Static |\n                BindingFlags.Instance);\n            if (prop == null && throwExceptionIfNotFound)\n                throw new NotSupportedException(\n                    String.Format(\n                        \"ERROR: Property '{0}' does not exist.\",\n                        propertyName)\n                    );\n            return prop != null;\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The data result.\n        /// </summary>\n        public List<T> Data { get; private set; }\n\n        /// <summary>\n        /// Zero-based index of current page.\n        /// </summary>\n        public int PageIndex { get; private set; }\n\n        /// <summary>\n        /// Number of items contained in each page.\n        /// </summary>\n        public int PageSize { get; private set; }\n\n        /// <summary>\n        /// Total items count\n        /// </summary>\n        public int TotalCount { get; private set; }\n\n        /// <summary>\n        /// Total pages count\n        /// </summary>\n        public int TotalPages { get; private set; }\n\n        /// <summary>\n        /// TRUE if the current page has a previous page, FALSE otherwise.\n        /// </summary>\n        public bool HasPreviousPage\n        {\n            get\n            {\n                return (PageIndex > 0);\n            }\n        }\n\n        /// <summary>\n        /// TRUE if the current page has a next page, FALSE otherwise.\n        /// </summary>\n        public bool HasNextPage\n        {\n            get\n            {\n                return ((PageIndex +1) < TotalPages);\n            }\n        }\n\n        /// <summary>\n        /// Sorting Column name (or null if none set)\n        /// </summary>\n        public string SortColumn { get; set; }\n\n        /// <summary>\n        /// Sorting Order (\"ASC\", \"DESC\" or null if none set)\n        /// </summary>\n        public string SortOrder { get; set; }\n\n        /// <summary>\n        /// Filter Column name (or null if none set)\n        /// </summary>\n        public string FilterColumn { get; set; }\n\n        /// <summary>\n        /// Filter Query string \n        /// (to be used within the given FilterColumn)\n        /// </summary>\n        public string FilterQuery { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Data/ApplicationDbContext.cs",
    "content": "﻿using IdentityServer4.EntityFramework.Options;\nusing Microsoft.AspNetCore.ApiAuthorization.IdentityServer;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Options;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Data\n{\n    public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>\n    {\n        #region Constructor\n        public ApplicationDbContext(\n            DbContextOptions options,\n            IOptions<OperationalStoreOptions> operationalStoreOptions) \n            : base(options, operationalStoreOptions)\n        {\n        }\n        #endregion Constructor\n\n        #region Methods\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            base.OnModelCreating(modelBuilder);\n\n            // Map Entity names to DB Table names\n            modelBuilder.Entity<City>().ToTable(\"Cities\");\n            modelBuilder.Entity<Country>().ToTable(\"Countries\");\n        }\n        #endregion Methods\n\n        #region Properties\n        public DbSet<City> Cities { get; set; }\n        public DbSet<Country> Countries { get; set; }\n        #endregion Properties\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Data/CityDTO.cs",
    "content": "﻿namespace WorldCities.Data\n{\n    public class CityDTO\n    {\n        public CityDTO() { }\n\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        public string Name_ASCII { get; set; }\n\n        public decimal Lat { get; set; }\n\n        public decimal Lon { get; set; }\n\n        public int CountryId { get; set; }\n\n        public string CountryName { get; set; }\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Data/CountryDTO.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class CountryDTO\n    {\n        public CountryDTO() { }\n\n        #region Properties\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n\n        public int TotCities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Data/IQueryableExtensions.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Query;\nusing Microsoft.EntityFrameworkCore.Query.SqlExpressions;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data\n{\n    public static class IQueryableExtension\n    {\n        public static string ToSql<T>(this IQueryable<T> query)\n        {\n            var enumerator = query.Provider\n                .Execute<IEnumerable<T>>(query.Expression).GetEnumerator();\n            var relationalCommandCache = enumerator\n                .Private(\"_relationalCommandCache\");\n            var selectExpression = relationalCommandCache\n                .Private<SelectExpression>(\"_selectExpression\");\n            var factory = relationalCommandCache\n                .Private<IQuerySqlGeneratorFactory>(\"_querySqlGeneratorFactory\");\n\n            var sqlGenerator = factory.Create();\n            var command = sqlGenerator.GetCommand(selectExpression);\n\n            string sql = command.CommandText;\n            return sql;\n        }\n\n        private static object Private(this object obj, string privateField) => \n            obj?.GetType()\n            .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?\n            .GetValue(obj);\n        private static T Private<T>(this object obj, string privateField) => \n            (T)obj?\n            .GetType()\n            .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?\n            .GetValue(obj);\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Data/Migrations/20191230002753_Identity.Designer.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    [Migration(\"20191230002753_Identity\")]\n    partial class Identity\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.1.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.DeviceFlowCodes\", b =>\n                {\n                    b.Property<string>(\"UserCode\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<string>(\"DeviceCode\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .IsRequired()\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.HasKey(\"UserCode\");\n\n                    b.HasIndex(\"DeviceCode\")\n                        .IsUnique();\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.ToTable(\"DeviceCodes\");\n                });\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.PersistedGrant\", b =>\n                {\n                    b.Property<string>(\"Key\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(50)\")\n                        .HasMaxLength(50);\n\n                    b.HasKey(\"Key\");\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.HasIndex(\"SubjectId\", \"ClientId\", \"Type\");\n\n                    b.ToTable(\"PersistedGrants\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRole\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedName\")\n                        .IsUnique()\n                        .HasName(\"RoleNameIndex\")\n                        .HasFilter(\"[NormalizedName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetRoleClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderKey\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderDisplayName\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"LoginProvider\", \"ProviderKey\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserLogins\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"UserId\", \"RoleId\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetUserRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Value\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"UserId\", \"LoginProvider\", \"Name\");\n\n                    b.ToTable(\"AspNetUserTokens\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.ApplicationUser\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<int>(\"AccessFailedCount\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Email\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<bool>(\"EmailConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<bool>(\"LockoutEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<DateTimeOffset?>(\"LockoutEnd\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.Property<string>(\"NormalizedEmail\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedUserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"PasswordHash\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"PhoneNumber\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"PhoneNumberConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"SecurityStamp\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"TwoFactorEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"UserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedEmail\")\n                        .HasName(\"EmailIndex\");\n\n                    b.HasIndex(\"NormalizedUserName\")\n                        .IsUnique()\n                        .HasName(\"UserNameIndex\")\n                        .HasFilter(\"[NormalizedUserName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetUsers\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Data/Migrations/20191230002753_Identity.cs",
    "content": "﻿using System;\nusing Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace WorldCities.Data.Migrations\n{\n    public partial class Identity : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.CreateTable(\n                name: \"AspNetRoles\",\n                columns: table => new\n                {\n                    Id = table.Column<string>(nullable: false),\n                    Name = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedName = table.Column<string>(maxLength: 256, nullable: true),\n                    ConcurrencyStamp = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetRoles\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUsers\",\n                columns: table => new\n                {\n                    Id = table.Column<string>(nullable: false),\n                    UserName = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),\n                    Email = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),\n                    EmailConfirmed = table.Column<bool>(nullable: false),\n                    PasswordHash = table.Column<string>(nullable: true),\n                    SecurityStamp = table.Column<string>(nullable: true),\n                    ConcurrencyStamp = table.Column<string>(nullable: true),\n                    PhoneNumber = table.Column<string>(nullable: true),\n                    PhoneNumberConfirmed = table.Column<bool>(nullable: false),\n                    TwoFactorEnabled = table.Column<bool>(nullable: false),\n                    LockoutEnd = table.Column<DateTimeOffset>(nullable: true),\n                    LockoutEnabled = table.Column<bool>(nullable: false),\n                    AccessFailedCount = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUsers\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"DeviceCodes\",\n                columns: table => new\n                {\n                    UserCode = table.Column<string>(maxLength: 200, nullable: false),\n                    DeviceCode = table.Column<string>(maxLength: 200, nullable: false),\n                    SubjectId = table.Column<string>(maxLength: 200, nullable: true),\n                    ClientId = table.Column<string>(maxLength: 200, nullable: false),\n                    CreationTime = table.Column<DateTime>(nullable: false),\n                    Expiration = table.Column<DateTime>(nullable: false),\n                    Data = table.Column<string>(maxLength: 50000, nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_DeviceCodes\", x => x.UserCode);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"PersistedGrants\",\n                columns: table => new\n                {\n                    Key = table.Column<string>(maxLength: 200, nullable: false),\n                    Type = table.Column<string>(maxLength: 50, nullable: false),\n                    SubjectId = table.Column<string>(maxLength: 200, nullable: true),\n                    ClientId = table.Column<string>(maxLength: 200, nullable: false),\n                    CreationTime = table.Column<DateTime>(nullable: false),\n                    Expiration = table.Column<DateTime>(nullable: true),\n                    Data = table.Column<string>(maxLength: 50000, nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_PersistedGrants\", x => x.Key);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetRoleClaims\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    RoleId = table.Column<string>(nullable: false),\n                    ClaimType = table.Column<string>(nullable: true),\n                    ClaimValue = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetRoleClaims\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_AspNetRoleClaims_AspNetRoles_RoleId\",\n                        column: x => x.RoleId,\n                        principalTable: \"AspNetRoles\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserClaims\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    UserId = table.Column<string>(nullable: false),\n                    ClaimType = table.Column<string>(nullable: true),\n                    ClaimValue = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserClaims\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserClaims_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserLogins\",\n                columns: table => new\n                {\n                    LoginProvider = table.Column<string>(maxLength: 128, nullable: false),\n                    ProviderKey = table.Column<string>(maxLength: 128, nullable: false),\n                    ProviderDisplayName = table.Column<string>(nullable: true),\n                    UserId = table.Column<string>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserLogins\", x => new { x.LoginProvider, x.ProviderKey });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserLogins_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserRoles\",\n                columns: table => new\n                {\n                    UserId = table.Column<string>(nullable: false),\n                    RoleId = table.Column<string>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserRoles\", x => new { x.UserId, x.RoleId });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserRoles_AspNetRoles_RoleId\",\n                        column: x => x.RoleId,\n                        principalTable: \"AspNetRoles\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserRoles_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserTokens\",\n                columns: table => new\n                {\n                    UserId = table.Column<string>(nullable: false),\n                    LoginProvider = table.Column<string>(maxLength: 128, nullable: false),\n                    Name = table.Column<string>(maxLength: 128, nullable: false),\n                    Value = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserTokens\", x => new { x.UserId, x.LoginProvider, x.Name });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserTokens_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetRoleClaims_RoleId\",\n                table: \"AspNetRoleClaims\",\n                column: \"RoleId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"RoleNameIndex\",\n                table: \"AspNetRoles\",\n                column: \"NormalizedName\",\n                unique: true,\n                filter: \"[NormalizedName] IS NOT NULL\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserClaims_UserId\",\n                table: \"AspNetUserClaims\",\n                column: \"UserId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserLogins_UserId\",\n                table: \"AspNetUserLogins\",\n                column: \"UserId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserRoles_RoleId\",\n                table: \"AspNetUserRoles\",\n                column: \"RoleId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"EmailIndex\",\n                table: \"AspNetUsers\",\n                column: \"NormalizedEmail\");\n\n            migrationBuilder.CreateIndex(\n                name: \"UserNameIndex\",\n                table: \"AspNetUsers\",\n                column: \"NormalizedUserName\",\n                unique: true,\n                filter: \"[NormalizedUserName] IS NOT NULL\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_DeviceCodes_DeviceCode\",\n                table: \"DeviceCodes\",\n                column: \"DeviceCode\",\n                unique: true);\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_DeviceCodes_Expiration\",\n                table: \"DeviceCodes\",\n                column: \"Expiration\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_PersistedGrants_Expiration\",\n                table: \"PersistedGrants\",\n                column: \"Expiration\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_PersistedGrants_SubjectId_ClientId_Type\",\n                table: \"PersistedGrants\",\n                columns: new[] { \"SubjectId\", \"ClientId\", \"Type\" });\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.DropTable(\n                name: \"AspNetRoleClaims\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserClaims\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserLogins\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserRoles\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserTokens\");\n\n            migrationBuilder.DropTable(\n                name: \"DeviceCodes\");\n\n            migrationBuilder.DropTable(\n                name: \"PersistedGrants\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetRoles\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUsers\");\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Data/Migrations/ApplicationDbContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    partial class ApplicationDbContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.1.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.DeviceFlowCodes\", b =>\n                {\n                    b.Property<string>(\"UserCode\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<string>(\"DeviceCode\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .IsRequired()\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.HasKey(\"UserCode\");\n\n                    b.HasIndex(\"DeviceCode\")\n                        .IsUnique();\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.ToTable(\"DeviceCodes\");\n                });\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.PersistedGrant\", b =>\n                {\n                    b.Property<string>(\"Key\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(50)\")\n                        .HasMaxLength(50);\n\n                    b.HasKey(\"Key\");\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.HasIndex(\"SubjectId\", \"ClientId\", \"Type\");\n\n                    b.ToTable(\"PersistedGrants\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRole\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedName\")\n                        .IsUnique()\n                        .HasName(\"RoleNameIndex\")\n                        .HasFilter(\"[NormalizedName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetRoleClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderKey\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderDisplayName\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"LoginProvider\", \"ProviderKey\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserLogins\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"UserId\", \"RoleId\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetUserRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Value\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"UserId\", \"LoginProvider\", \"Name\");\n\n                    b.ToTable(\"AspNetUserTokens\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.ApplicationUser\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<int>(\"AccessFailedCount\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Email\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<bool>(\"EmailConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<bool>(\"LockoutEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<DateTimeOffset?>(\"LockoutEnd\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.Property<string>(\"NormalizedEmail\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedUserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"PasswordHash\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"PhoneNumber\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"PhoneNumberConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"SecurityStamp\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"TwoFactorEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"UserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedEmail\")\n                        .HasName(\"EmailIndex\");\n\n                    b.HasIndex(\"NormalizedUserName\")\n                        .IsUnique()\n                        .HasName(\"UserNameIndex\")\n                        .HasFilter(\"[NormalizedUserName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetUsers\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Data/Models/ApplicationUser.cs",
    "content": "﻿using Microsoft.AspNetCore.Identity;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class ApplicationUser : IdentityUser\n    {\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Data/Models/City.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class City\n    {\n        #region Constructor\n        public City()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this City\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// City name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// City name (in ASCII format)\n        /// </summary>\n        public string Name_ASCII { get; set; }\n\n        /// <summary>\n        /// City latitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lat { get; set; }\n\n        /// <summary>\n        /// City longitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lon { get; set; }\n\n        /// <summary>\n        /// Country Id (foreign key)\n        /// </summary>\n        [ForeignKey(\"Country\")]\n        public int CountryId { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// The country related to this city.\n        /// </summary>\n        public virtual Country Country { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Data/Models/Country.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class Country\n    {\n        #region Constructor\n        public Country()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this Country\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// Country name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-2 format)\n        /// </summary>\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-3 format)\n        /// </summary>\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n        #endregion\n\n        #region Client-side properties\n        /// <summary>\n        /// The number of cities related to this country.\n        /// </summary>\n        [NotMapped]\n        public int TotCities\n        {\n            get\n            {\n                return (Cities != null)\n                    ? Cities.Count\n                    : _TotCities;\n            }\n            set { _TotCities = value; }\n        }\n\n        private int _TotCities = 0;\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// A list containing all the cities related to this country.\n        /// </summary>\n        [JsonIgnore]\n        public virtual List<City> Cities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_11/WorldCities/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Pages/Shared/_LoginPartial.cshtml",
    "content": "﻿@using Microsoft.AspNetCore.Identity\n@using WorldCities.Data.Models;\n@inject SignInManager<ApplicationUser> SignInManager\n@inject UserManager<ApplicationUser> UserManager\n\n@{\n    string returnUrl = null;\n    var query = ViewContext.HttpContext.Request.Query;\n    if (query.ContainsKey(\"returnUrl\"))\n    {\n        returnUrl = query[\"returnUrl\"];\n    }\n}\n\n<ul class=\"navbar-nav\">\n    @if (SignInManager.IsSignedIn(User))\n    {\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Manage/Index\" title=\"Manage\">Hello @User.Identity.Name!</a>\n        </li>\n        <li class=\"nav-item\">\n            <form class=\"form-inline\" asp-area=\"Identity\" asp-page=\"/Account/Logout\" asp-route-returnUrl=\"/\">\n                <button type=\"submit\" class=\"nav-link btn btn-link text-dark\">Logout</button>\n            </form>\n        </li>\n    }\n    else\n    {\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Register\" asp-route-returnUrl=\"@returnUrl\">Register</a>\n        </li>\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Login\" asp-route-returnUrl=\"@returnUrl\">Login</a>\n        </li>\n    }\n</ul>\n"
  },
  {
    "path": "Chapter_11/WorldCities/Pages/_ViewImports.cshtml",
    "content": "@using WorldCities\n@namespace WorldCities.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_11/WorldCities/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/Startup.cs",
    "content": "using Microsoft.AspNetCore.Authentication;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing System.Text.Json;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\nusing Microsoft.AspNetCore.StaticFiles;\n\nnamespace WorldCities\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews()\n                .AddJsonOptions(options => {\n            // set this option to TRUE to indent the JSON output\n            options.JsonSerializerOptions.WriteIndented = true;\n            // set this option to NULL to use PascalCase instead of CamelCase (default)\n            // options.JsonSerializerOptions.PropertyNamingPolicy = null;\n        });\n\n\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            // Add EntityFramework support for SqlServer.\n            services.AddEntityFrameworkSqlServer();\n\n            // Add ApplicationDbContext.\n            services.AddDbContext<ApplicationDbContext>(options =>\n                options.UseSqlServer(\n                    Configuration.GetConnectionString(\"DefaultConnection\")\n                    )\n            );\n\n            // Add ASP.NET Core Identity support\n            services.AddDefaultIdentity<ApplicationUser>(options =>\n            {\n                options.SignIn.RequireConfirmedAccount = true;\n                options.Password.RequireLowercase = true;\n                options.Password.RequireUppercase = true;\n                options.Password.RequireDigit = true;\n                options.Password.RequireNonAlphanumeric = true;\n                options.Password.RequiredLength = 8;\n            })\n                .AddRoles<IdentityRole>()\n                .AddEntityFrameworkStores<ApplicationDbContext>();\n\n            services.AddIdentityServer()\n                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();\n\n            services.AddAuthentication()\n                .AddIdentityServerJwt();\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n\n            // add .webmanifest MIME-type support\n            FileExtensionContentTypeProvider provider = new FileExtensionContentTypeProvider();\n            provider.Mappings[\".webmanifest\"] = \"application/manifest+json\";\n\n            app.UseStaticFiles(new StaticFileOptions()\n            {\n                ContentTypeProvider = provider,\n                OnPrepareResponse = (context) =>\n                {\n                    if (context.File.Name == \"isOnline.txt\")\n                    {\n                        // disable caching for these files\n                        context.Context.Response.Headers.Add(\"Cache-Control\", \"no-cache, no-store\");\n                        context.Context.Response.Headers.Add(\"Expires\", \"-1\");\n                    }\n                }\n            });\n\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles(new StaticFileOptions()\n                {\n                    ContentTypeProvider = provider\n                });\n            }\n\n            app.UseRouting();\n\n            app.UseAuthentication();\n            app.UseIdentityServer();\n            app.UseAuthorization();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n\n                endpoints.MapRazorPages();\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/WorldCities.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"EPPlus\" Version=\"4.5.3.2\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.ApiAuthorization.IdentityServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.UI\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Debug\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" Version=\"3.1.0\" />\n    <PackageReference Include=\"System.Linq.Dynamic.Core\" Version=\"1.0.20\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <None Remove=\"Properties\\PublishProfiles\\AdvancedSettings.pubxml\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Folder Include=\"Data\\Migrations\\\" />\n    <Folder Include=\"Properties\\PublishProfiles\\\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_11/WorldCities/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  },\n  \"IdentityServer\": {\n    \"Key\": {\n      \"Type\": \"Development\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/appsettings.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"Server=localhost\\\\SQLEXPRESS;Database=WorldCities;User Id=WorldCities;Password=MyVeryOwn$721;Integrated Security=False;MultipleActiveResultSets=True\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"IdentityServer\": {\n    \"Clients\": {\n      \"WorldCities\": {\n        \"Profile\": \"IdentityServerSPA\"\n      }\n    }\n  },\n  \"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/wwwroot/isOnline.txt",
    "content": "﻿."
  },
  {
    "path": "Chapter_11/WorldCities/wwwroot/manifest.webmanifest",
    "content": "{\n  \"name\": \"HealthCheck\",\n  \"short_name\": \"HealthCheck\",\n  \"theme_color\": \"#2196f3\",\n  \"background_color\": \"#2196f3\",\n  \"display\": \"standalone\",\n  \"Scope\": \"/\",\n  \"start_url\": \"/\",\n  \"icons\": [\n    {\n      \"src\": \"assets/icons/icon-72x72.png\",\n      \"sizes\": \"72x72\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-96x96.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-128x128.png\",\n      \"sizes\": \"128x128\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-144x144.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-152x152.png\",\n      \"sizes\": \"152x152\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-384x384.png\",\n      \"sizes\": \"384x384\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"splash_pages\": null\n}\n"
  },
  {
    "path": "Chapter_11/WorldCities/wwwroot/ngsw-worker.js",
    "content": "(function () {\n    'use strict';\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * Adapts the service worker to its runtime environment.\n     *\n     * Mostly, this is used to mock out identifiers which are otherwise read\n     * from the global scope.\n     */\n    class Adapter {\n        constructor(scope) {\n            // Suffixing `ngsw` with the baseHref to avoid clash of cache names\n            // for SWs with different scopes on the same domain.\n            const baseHref = this.parseUrl(scope.registration.scope).path;\n            this.cacheNamePrefix = 'ngsw:' + baseHref;\n        }\n        /**\n         * Wrapper around the `Request` constructor.\n         */\n        newRequest(input, init) {\n            return new Request(input, init);\n        }\n        /**\n         * Wrapper around the `Response` constructor.\n         */\n        newResponse(body, init) { return new Response(body, init); }\n        /**\n         * Wrapper around the `Headers` constructor.\n         */\n        newHeaders(headers) { return new Headers(headers); }\n        /**\n         * Test if a given object is an instance of `Client`.\n         */\n        isClient(source) { return (source instanceof Client); }\n        /**\n         * Read the current UNIX time in milliseconds.\n         */\n        get time() { return Date.now(); }\n        /**\n         * Extract the pathname of a URL.\n         */\n        parseUrl(url, relativeTo) {\n            // Workaround a Safari bug, see\n            // https://github.com/angular/angular/issues/31061#issuecomment-503637978\n            const parsed = !relativeTo ? new URL(url) : new URL(url, relativeTo);\n            return { origin: parsed.origin, path: parsed.pathname, search: parsed.search };\n        }\n        /**\n         * Wait for a given amount of time before completing a Promise.\n         */\n        timeout(ms) {\n            return new Promise(resolve => { setTimeout(() => resolve(), ms); });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * An error returned in rejected promises if the given key is not found in the table.\n     */\n    class NotFound {\n        constructor(table, key) {\n            this.table = table;\n            this.key = key;\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * An implementation of a `Database` that uses the `CacheStorage` API to serialize\n     * state within mock `Response` objects.\n     */\n    class CacheDatabase {\n        constructor(scope, adapter) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.tables = new Map();\n        }\n        'delete'(name) {\n            if (this.tables.has(name)) {\n                this.tables.delete(name);\n            }\n            return this.scope.caches.delete(`${this.adapter.cacheNamePrefix}:db:${name}`);\n        }\n        list() {\n            return this.scope.caches.keys().then(keys => keys.filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:db:`)));\n        }\n        open(name) {\n            if (!this.tables.has(name)) {\n                const table = this.scope.caches.open(`${this.adapter.cacheNamePrefix}:db:${name}`)\n                    .then(cache => new CacheTable(name, cache, this.adapter));\n                this.tables.set(name, table);\n            }\n            return this.tables.get(name);\n        }\n    }\n    /**\n     * A `Table` backed by a `Cache`.\n     */\n    class CacheTable {\n        constructor(table, cache, adapter) {\n            this.table = table;\n            this.cache = cache;\n            this.adapter = adapter;\n        }\n        request(key) { return this.adapter.newRequest('/' + key); }\n        'delete'(key) { return this.cache.delete(this.request(key)); }\n        keys() {\n            return this.cache.keys().then(requests => requests.map(req => req.url.substr(1)));\n        }\n        read(key) {\n            return this.cache.match(this.request(key)).then(res => {\n                if (res === undefined) {\n                    return Promise.reject(new NotFound(this.table, key));\n                }\n                return res.json();\n            });\n        }\n        write(key, value) {\n            return this.cache.put(this.request(key), this.adapter.newResponse(JSON.stringify(value)));\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var UpdateCacheStatus;\n    (function (UpdateCacheStatus) {\n        UpdateCacheStatus[UpdateCacheStatus[\"NOT_CACHED\"] = 0] = \"NOT_CACHED\";\n        UpdateCacheStatus[UpdateCacheStatus[\"CACHED_BUT_UNUSED\"] = 1] = \"CACHED_BUT_UNUSED\";\n        UpdateCacheStatus[UpdateCacheStatus[\"CACHED\"] = 2] = \"CACHED\";\n    })(UpdateCacheStatus || (UpdateCacheStatus = {}));\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    class SwCriticalError extends Error {\n        constructor() {\n            super(...arguments);\n            this.isCritical = true;\n        }\n    }\n    function errorToString(error) {\n        if (error instanceof Error) {\n            return `${error.message}\\n${error.stack}`;\n        }\n        else {\n            return `${error}`;\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * Compute the SHA1 of the given string\n     *\n     * see http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf\n     *\n     * WARNING: this function has not been designed not tested with security in mind.\n     *          DO NOT USE IT IN A SECURITY SENSITIVE CONTEXT.\n     *\n     * Borrowed from @angular/compiler/src/i18n/digest.ts\n     */\n    function sha1(str) {\n        const utf8 = str;\n        const words32 = stringToWords32(utf8, Endian.Big);\n        return _sha1(words32, utf8.length * 8);\n    }\n    function sha1Binary(buffer) {\n        const words32 = arrayBufferToWords32(buffer, Endian.Big);\n        return _sha1(words32, buffer.byteLength * 8);\n    }\n    function _sha1(words32, len) {\n        const w = [];\n        let [a, b, c, d, e] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0];\n        words32[len >> 5] |= 0x80 << (24 - len % 32);\n        words32[((len + 64 >> 9) << 4) + 15] = len;\n        for (let i = 0; i < words32.length; i += 16) {\n            const [h0, h1, h2, h3, h4] = [a, b, c, d, e];\n            for (let j = 0; j < 80; j++) {\n                if (j < 16) {\n                    w[j] = words32[i + j];\n                }\n                else {\n                    w[j] = rol32(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);\n                }\n                const [f, k] = fk(j, b, c, d);\n                const temp = [rol32(a, 5), f, e, k, w[j]].reduce(add32);\n                [e, d, c, b, a] = [d, c, rol32(b, 30), a, temp];\n            }\n            [a, b, c, d, e] = [add32(a, h0), add32(b, h1), add32(c, h2), add32(d, h3), add32(e, h4)];\n        }\n        return byteStringToHexString(words32ToByteString([a, b, c, d, e]));\n    }\n    function add32(a, b) {\n        return add32to64(a, b)[1];\n    }\n    function add32to64(a, b) {\n        const low = (a & 0xffff) + (b & 0xffff);\n        const high = (a >>> 16) + (b >>> 16) + (low >>> 16);\n        return [high >>> 16, (high << 16) | (low & 0xffff)];\n    }\n    // Rotate a 32b number left `count` position\n    function rol32(a, count) {\n        return (a << count) | (a >>> (32 - count));\n    }\n    var Endian;\n    (function (Endian) {\n        Endian[Endian[\"Little\"] = 0] = \"Little\";\n        Endian[Endian[\"Big\"] = 1] = \"Big\";\n    })(Endian || (Endian = {}));\n    function fk(index, b, c, d) {\n        if (index < 20) {\n            return [(b & c) | (~b & d), 0x5a827999];\n        }\n        if (index < 40) {\n            return [b ^ c ^ d, 0x6ed9eba1];\n        }\n        if (index < 60) {\n            return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];\n        }\n        return [b ^ c ^ d, 0xca62c1d6];\n    }\n    function stringToWords32(str, endian) {\n        const size = (str.length + 3) >>> 2;\n        const words32 = [];\n        for (let i = 0; i < size; i++) {\n            words32[i] = wordAt(str, i * 4, endian);\n        }\n        return words32;\n    }\n    function arrayBufferToWords32(buffer, endian) {\n        const size = (buffer.byteLength + 3) >>> 2;\n        const words32 = [];\n        const view = new Uint8Array(buffer);\n        for (let i = 0; i < size; i++) {\n            words32[i] = wordAt(view, i * 4, endian);\n        }\n        return words32;\n    }\n    function byteAt(str, index) {\n        if (typeof str === 'string') {\n            return index >= str.length ? 0 : str.charCodeAt(index) & 0xff;\n        }\n        else {\n            return index >= str.byteLength ? 0 : str[index] & 0xff;\n        }\n    }\n    function wordAt(str, index, endian) {\n        let word = 0;\n        if (endian === Endian.Big) {\n            for (let i = 0; i < 4; i++) {\n                word += byteAt(str, index + i) << (24 - 8 * i);\n            }\n        }\n        else {\n            for (let i = 0; i < 4; i++) {\n                word += byteAt(str, index + i) << 8 * i;\n            }\n        }\n        return word;\n    }\n    function words32ToByteString(words32) {\n        return words32.reduce((str, word) => str + word32ToByteString(word), '');\n    }\n    function word32ToByteString(word) {\n        let str = '';\n        for (let i = 0; i < 4; i++) {\n            str += String.fromCharCode((word >>> 8 * (3 - i)) & 0xff);\n        }\n        return str;\n    }\n    function byteStringToHexString(str) {\n        let hex = '';\n        for (let i = 0; i < str.length; i++) {\n            const b = byteAt(str, i);\n            hex += (b >>> 4).toString(16) + (b & 0x0f).toString(16);\n        }\n        return hex.toLowerCase();\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    /**\n     * A group of assets that are cached in a `Cache` and managed by a given policy.\n     *\n     * Concrete classes derive from this base and specify the exact caching policy.\n     */\n    class AssetGroup {\n        constructor(scope, adapter, idle, config, hashes, db, prefix) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.idle = idle;\n            this.config = config;\n            this.hashes = hashes;\n            this.db = db;\n            this.prefix = prefix;\n            /**\n             * A deduplication cache, to make sure the SW never makes two network requests\n             * for the same resource at once. Managed by `fetchAndCacheOnce`.\n             */\n            this.inFlightRequests = new Map();\n            /**\n             * Regular expression patterns.\n             */\n            this.patterns = [];\n            this.name = config.name;\n            // Patterns in the config are regular expressions disguised as strings. Breathe life into them.\n            this.patterns = this.config.patterns.map(pattern => new RegExp(pattern));\n            // This is the primary cache, which holds all of the cached requests for this group. If a\n            // resource\n            // isn't in this cache, it hasn't been fetched yet.\n            this.cache = this.scope.caches.open(`${this.prefix}:${this.config.name}:cache`);\n            // This is the metadata table, which holds specific information for each cached URL, such as\n            // the timestamp of when it was added to the cache.\n            this.metadata = this.db.open(`${this.prefix}:${this.config.name}:meta`);\n            // Determine the origin from the registration scope. This is used to differentiate between\n            // relative and absolute URLs.\n            this.origin = this.adapter.parseUrl(this.scope.registration.scope).origin;\n        }\n        cacheStatus(url) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                const meta = yield this.metadata;\n                const res = yield cache.match(this.adapter.newRequest(url));\n                if (res === undefined) {\n                    return UpdateCacheStatus.NOT_CACHED;\n                }\n                try {\n                    const data = yield meta.read(url);\n                    if (!data.used) {\n                        return UpdateCacheStatus.CACHED_BUT_UNUSED;\n                    }\n                }\n                catch (_) {\n                    // Error on the side of safety and assume cached.\n                }\n                return UpdateCacheStatus.CACHED;\n            });\n        }\n        /**\n         * Clean up all the cached data for this group.\n         */\n        cleanup() {\n            return __awaiter(this, void 0, void 0, function* () {\n                yield this.scope.caches.delete(`${this.prefix}:${this.config.name}:cache`);\n                yield this.db.delete(`${this.prefix}:${this.config.name}:meta`);\n            });\n        }\n        /**\n         * Process a request for a given resource and return it, or return null if it's not available.\n         */\n        handleFetch(req, ctx) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                // Either the request matches one of the known resource URLs, one of the patterns for\n                // dynamically matched URLs, or neither. Determine which is the case for this request in\n                // order to decide how to handle it.\n                if (this.config.urls.indexOf(url) !== -1 || this.patterns.some(pattern => pattern.test(url))) {\n                    // This URL matches a known resource. Either it's been cached already or it's missing, in\n                    // which case it needs to be loaded from the network.\n                    // Open the cache to check whether this resource is present.\n                    const cache = yield this.cache;\n                    // Look for a cached response. If one exists, it can be used to resolve the fetch\n                    // operation.\n                    const cachedResponse = yield cache.match(req);\n                    if (cachedResponse !== undefined) {\n                        // A response has already been cached (which presumably matches the hash for this\n                        // resource). Check whether it's safe to serve this resource from cache.\n                        if (this.hashes.has(url)) {\n                            // This resource has a hash, and thus is versioned by the manifest. It's safe to return\n                            // the response.\n                            return cachedResponse;\n                        }\n                        else {\n                            // This resource has no hash, and yet exists in the cache. Check how old this request is\n                            // to make sure it's still usable.\n                            if (yield this.needToRevalidate(req, cachedResponse)) {\n                                this.idle.schedule(`revalidate(${this.prefix}, ${this.config.name}): ${req.url}`, () => __awaiter(this, void 0, void 0, function* () { yield this.fetchAndCacheOnce(req); }));\n                            }\n                            // In either case (revalidation or not), the cached response must be good.\n                            return cachedResponse;\n                        }\n                    }\n                    // No already-cached response exists, so attempt a fetch/cache operation. The original request\n                    // may specify things like credential inclusion, but for assets these are not honored in order\n                    // to avoid issues with opaque responses. The SW requests the data itself.\n                    const res = yield this.fetchAndCacheOnce(this.adapter.newRequest(req.url));\n                    // If this is successful, the response needs to be cloned as it might be used to respond to\n                    // multiple fetch operations at the same time.\n                    return res.clone();\n                }\n                else {\n                    return null;\n                }\n            });\n        }\n        getConfigUrl(url) {\n            // If the URL is relative to the SW's own origin, then only consider the path relative to\n            // the domain root. Determine this by checking the URL's origin against the SW's.\n            const parsed = this.adapter.parseUrl(url, this.scope.registration.scope);\n            if (parsed.origin === this.origin) {\n                // The URL is relative to the SW's origin domain.\n                return parsed.path;\n            }\n            else {\n                return url;\n            }\n        }\n        /**\n         * Some resources are cached without a hash, meaning that their expiration is controlled\n         * by HTTP caching headers. Check whether the given request/response pair is still valid\n         * per the caching headers.\n         */\n        needToRevalidate(req, res) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Three different strategies apply here:\n                // 1) The request has a Cache-Control header, and thus expiration needs to be based on its age.\n                // 2) The request has an Expires header, and expiration is based on the current timestamp.\n                // 3) The request has no applicable caching headers, and must be revalidated.\n                if (res.headers.has('Cache-Control')) {\n                    // Figure out if there is a max-age directive in the Cache-Control header.\n                    const cacheControl = res.headers.get('Cache-Control');\n                    const cacheDirectives = cacheControl\n                        // Directives are comma-separated within the Cache-Control header value.\n                        .split(',')\n                        // Make sure each directive doesn't have extraneous whitespace.\n                        .map(v => v.trim())\n                        // Some directives have values (like maxage and s-maxage)\n                        .map(v => v.split('='));\n                    // Lowercase all the directive names.\n                    cacheDirectives.forEach(v => v[0] = v[0].toLowerCase());\n                    // Find the max-age directive, if one exists.\n                    const maxAgeDirective = cacheDirectives.find(v => v[0] === 'max-age');\n                    const cacheAge = maxAgeDirective ? maxAgeDirective[1] : undefined;\n                    if (!cacheAge) {\n                        // No usable TTL defined. Must assume that the response is stale.\n                        return true;\n                    }\n                    try {\n                        const maxAge = 1000 * parseInt(cacheAge);\n                        // Determine the origin time of this request. If the SW has metadata on the request (which\n                        // it\n                        // should), it will have the time the request was added to the cache. If it doesn't for some\n                        // reason, the request may have a Date header which will serve the same purpose.\n                        let ts;\n                        try {\n                            // Check the metadata table. If a timestamp is there, use it.\n                            const metaTable = yield this.metadata;\n                            ts = (yield metaTable.read(req.url)).ts;\n                        }\n                        catch (_a) {\n                            // Otherwise, look for a Date header.\n                            const date = res.headers.get('Date');\n                            if (date === null) {\n                                // Unable to determine when this response was created. Assume that it's stale, and\n                                // revalidate it.\n                                return true;\n                            }\n                            ts = Date.parse(date);\n                        }\n                        const age = this.adapter.time - ts;\n                        return age < 0 || age > maxAge;\n                    }\n                    catch (_b) {\n                        // Assume stale.\n                        return true;\n                    }\n                }\n                else if (res.headers.has('Expires')) {\n                    // Determine if the expiration time has passed.\n                    const expiresStr = res.headers.get('Expires');\n                    try {\n                        // The request needs to be revalidated if the current time is later than the expiration\n                        // time, if it parses correctly.\n                        return this.adapter.time > Date.parse(expiresStr);\n                    }\n                    catch (_c) {\n                        // The expiration date failed to parse, so revalidate as a precaution.\n                        return true;\n                    }\n                }\n                else {\n                    // No way to evaluate staleness, so assume the response is already stale.\n                    return true;\n                }\n            });\n        }\n        /**\n         * Fetch the complete state of a cached resource, or return null if it's not found.\n         */\n        fetchFromCacheOnly(url) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                const metaTable = yield this.metadata;\n                // Lookup the response in the cache.\n                const response = yield cache.match(this.adapter.newRequest(url));\n                if (response === undefined) {\n                    // It's not found, return null.\n                    return null;\n                }\n                // Next, lookup the cached metadata.\n                let metadata = undefined;\n                try {\n                    metadata = yield metaTable.read(url);\n                }\n                catch (_a) {\n                    // Do nothing, not found. This shouldn't happen, but it can be handled.\n                }\n                // Return both the response and any available metadata.\n                return { response, metadata };\n            });\n        }\n        /**\n         * Lookup all resources currently stored in the cache which have no associated hash.\n         */\n        unhashedResources() {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                // Start with the set of all cached URLs.\n                return (yield cache.keys())\n                    .map(request => request.url)\n                    // Exclude the URLs which have hashes.\n                    .filter(url => !this.hashes.has(url));\n            });\n        }\n        /**\n         * Fetch the given resource from the network, and cache it if able.\n         */\n        fetchAndCacheOnce(req, used = true) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // The `inFlightRequests` map holds information about which caching operations are currently\n                // underway for known resources. If this request appears there, another \"thread\" is already\n                // in the process of caching it, and this work should not be duplicated.\n                if (this.inFlightRequests.has(req.url)) {\n                    // There is a caching operation already in progress for this request. Wait for it to\n                    // complete, and hopefully it will have yielded a useful response.\n                    return this.inFlightRequests.get(req.url);\n                }\n                // No other caching operation is being attempted for this resource, so it will be owned here.\n                // Go to the network and get the correct version.\n                const fetchOp = this.fetchFromNetwork(req);\n                // Save this operation in `inFlightRequests` so any other \"thread\" attempting to cache it\n                // will block on this chain instead of duplicating effort.\n                this.inFlightRequests.set(req.url, fetchOp);\n                // Make sure this attempt is cleaned up properly on failure.\n                try {\n                    // Wait for a response. If this fails, the request will remain in `inFlightRequests`\n                    // indefinitely.\n                    const res = yield fetchOp;\n                    // It's very important that only successful responses are cached. Unsuccessful responses\n                    // should never be cached as this can completely break applications.\n                    if (!res.ok) {\n                        throw new Error(`Response not Ok (fetchAndCacheOnce): request for ${req.url} returned response ${res.status} ${res.statusText}`);\n                    }\n                    try {\n                        // This response is safe to cache (as long as it's cloned). Wait until the cache operation\n                        // is complete.\n                        const cache = yield this.scope.caches.open(`${this.prefix}:${this.config.name}:cache`);\n                        yield cache.put(req, res.clone());\n                        // If the request is not hashed, update its metadata, especially the timestamp. This is\n                        // needed for future determination of whether this cached response is stale or not.\n                        if (!this.hashes.has(req.url)) {\n                            // Metadata is tracked for requests that are unhashed.\n                            const meta = { ts: this.adapter.time, used };\n                            const metaTable = yield this.metadata;\n                            yield metaTable.write(req.url, meta);\n                        }\n                        return res;\n                    }\n                    catch (err) {\n                        // Among other cases, this can happen when the user clears all data through the DevTools,\n                        // but the SW is still running and serving another tab. In that case, trying to write to the\n                        // caches throws an `Entry was not found` error.\n                        // If this happens the SW can no longer work correctly. This situation is unrecoverable.\n                        throw new SwCriticalError(`Failed to update the caches for request to '${req.url}' (fetchAndCacheOnce): ${errorToString(err)}`);\n                    }\n                }\n                finally {\n                    // Finally, it can be removed from `inFlightRequests`. This might result in a double-remove\n                    // if some other chain was already making this request too, but that won't hurt anything.\n                    this.inFlightRequests.delete(req.url);\n                }\n            });\n        }\n        fetchFromNetwork(req, redirectLimit = 3) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Make a cache-busted request for the resource.\n                const res = yield this.cacheBustedFetchFromNetwork(req);\n                // Check for redirected responses, and follow the redirects.\n                if (res['redirected'] && !!res.url) {\n                    // If the redirect limit is exhausted, fail with an error.\n                    if (redirectLimit === 0) {\n                        throw new SwCriticalError(`Response hit redirect limit (fetchFromNetwork): request redirected too many times, next is ${res.url}`);\n                    }\n                    // Unwrap the redirect directly.\n                    return this.fetchFromNetwork(this.adapter.newRequest(res.url), redirectLimit - 1);\n                }\n                return res;\n            });\n        }\n        /**\n         * Load a particular asset from the network, accounting for hash validation.\n         */\n        cacheBustedFetchFromNetwork(req) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                // If a hash is available for this resource, then compare the fetched version with the\n                // canonical hash. Otherwise, the network version will have to be trusted.\n                if (this.hashes.has(url)) {\n                    // It turns out this resource does have a hash. Look it up. Unless the fetched version\n                    // matches this hash, it's invalid and the whole manifest may need to be thrown out.\n                    const canonicalHash = this.hashes.get(url);\n                    // Ideally, the resource would be requested with cache-busting to guarantee the SW gets\n                    // the freshest version. However, doing this would eliminate any chance of the response\n                    // being in the HTTP cache. Given that the browser has recently actively loaded the page,\n                    // it's likely that many of the responses the SW needs to cache are in the HTTP cache and\n                    // are fresh enough to use. In the future, this could be done by setting cacheMode to\n                    // *only* check the browser cache for a cached version of the resource, when cacheMode is\n                    // fully supported. For now, the resource is fetched directly, without cache-busting, and\n                    // if the hash test fails a cache-busted request is tried before concluding that the\n                    // resource isn't correct. This gives the benefit of acceleration via the HTTP cache\n                    // without the risk of stale data, at the expense of a duplicate request in the event of\n                    // a stale response.\n                    // Fetch the resource from the network (possibly hitting the HTTP cache).\n                    const networkResult = yield this.safeFetch(req);\n                    // Decide whether a cache-busted request is necessary. It might be for two independent\n                    // reasons: either the non-cache-busted request failed (hopefully transiently) or if the\n                    // hash of the content retrieved does not match the canonical hash from the manifest. It's\n                    // only valid to access the content of the first response if the request was successful.\n                    let makeCacheBustedRequest = networkResult.ok;\n                    if (makeCacheBustedRequest) {\n                        // The request was successful. A cache-busted request is only necessary if the hashes\n                        // don't match. Compare them, making sure to clone the response so it can be used later\n                        // if it proves to be valid.\n                        const fetchedHash = sha1Binary(yield networkResult.clone().arrayBuffer());\n                        makeCacheBustedRequest = (fetchedHash !== canonicalHash);\n                    }\n                    // Make a cache busted request to the network, if necessary.\n                    if (makeCacheBustedRequest) {\n                        // Hash failure, the version that was retrieved under the default URL did not have the\n                        // hash expected. This could be because the HTTP cache got in the way and returned stale\n                        // data, or because the version on the server really doesn't match. A cache-busting\n                        // request will differentiate these two situations.\n                        // TODO: handle case where the URL has parameters already (unlikely for assets).\n                        const cacheBustReq = this.adapter.newRequest(this.cacheBust(req.url));\n                        const cacheBustedResult = yield this.safeFetch(cacheBustReq);\n                        // If the response was unsuccessful, there's nothing more that can be done.\n                        if (!cacheBustedResult.ok) {\n                            throw new SwCriticalError(`Response not Ok (cacheBustedFetchFromNetwork): cache busted request for ${req.url} returned response ${cacheBustedResult.status} ${cacheBustedResult.statusText}`);\n                        }\n                        // Hash the contents.\n                        const cacheBustedHash = sha1Binary(yield cacheBustedResult.clone().arrayBuffer());\n                        // If the cache-busted version doesn't match, then the manifest is not an accurate\n                        // representation of the server's current set of files, and the SW should give up.\n                        if (canonicalHash !== cacheBustedHash) {\n                            throw new SwCriticalError(`Hash mismatch (cacheBustedFetchFromNetwork): ${req.url}: expected ${canonicalHash}, got ${cacheBustedHash} (after cache busting)`);\n                        }\n                        // If it does match, then use the cache-busted result.\n                        return cacheBustedResult;\n                    }\n                    // Excellent, the version from the network matched on the first try, with no need for\n                    // cache-busting. Use it.\n                    return networkResult;\n                }\n                else {\n                    // This URL doesn't exist in our hash database, so it must be requested directly.\n                    return this.safeFetch(req);\n                }\n            });\n        }\n        /**\n         * Possibly update a resource, if it's expired and needs to be updated. A no-op otherwise.\n         */\n        maybeUpdate(updateFrom, req, cache) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                const meta = yield this.metadata;\n                // Check if this resource is hashed and already exists in the cache of a prior version.\n                if (this.hashes.has(url)) {\n                    const hash = this.hashes.get(url);\n                    // Check the caches of prior versions, using the hash to ensure the correct version of\n                    // the resource is loaded.\n                    const res = yield updateFrom.lookupResourceWithHash(url, hash);\n                    // If a previously cached version was available, copy it over to this cache.\n                    if (res !== null) {\n                        // Copy to this cache.\n                        yield cache.put(req, res);\n                        yield meta.write(req.url, { ts: this.adapter.time, used: false });\n                        // No need to do anything further with this resource, it's now cached properly.\n                        return true;\n                    }\n                }\n                // No up-to-date version of this resource could be found.\n                return false;\n            });\n        }\n        /**\n         * Construct a cache-busting URL for a given URL.\n         */\n        cacheBust(url) {\n            return url + (url.indexOf('?') === -1 ? '?' : '&') + 'ngsw-cache-bust=' + Math.random();\n        }\n        safeFetch(req) {\n            return __awaiter(this, void 0, void 0, function* () {\n                try {\n                    return yield this.scope.fetch(req);\n                }\n                catch (_a) {\n                    return this.adapter.newResponse('', {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n    /**\n     * An `AssetGroup` that prefetches all of its resources during initialization.\n     */\n    class PrefetchAssetGroup extends AssetGroup {\n        initializeFully(updateFrom) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Open the cache which actually holds requests.\n                const cache = yield this.cache;\n                // Cache all known resources serially. As this reduce proceeds, each Promise waits\n                // on the last before starting the fetch/cache operation for the next request. Any\n                // errors cause fall-through to the final Promise which rejects.\n                yield this.config.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                    // Wait on all previous operations to complete.\n                    yield previous;\n                    // Construct the Request for this url.\n                    const req = this.adapter.newRequest(url);\n                    // First, check the cache to see if there is already a copy of this resource.\n                    const alreadyCached = (yield cache.match(req)) !== undefined;\n                    // If the resource is in the cache already, it can be skipped.\n                    if (alreadyCached) {\n                        return;\n                    }\n                    // If an update source is available.\n                    if (updateFrom !== undefined && (yield this.maybeUpdate(updateFrom, req, cache))) {\n                        return;\n                    }\n                    // Otherwise, go to the network and hopefully cache the response (if successful).\n                    yield this.fetchAndCacheOnce(req, false);\n                }), Promise.resolve());\n                // Handle updating of unknown (unhashed) resources. This is only possible if there's\n                // a source to update from.\n                if (updateFrom !== undefined) {\n                    const metaTable = yield this.metadata;\n                    // Select all of the previously cached resources. These are cached unhashed resources\n                    // from previous versions of the app, in any asset group.\n                    yield (yield updateFrom.previouslyCachedResources())\n                        // First, narrow down the set of resources to those which are handled by this group.\n                        // Either it's a known URL, or it matches a given pattern.\n                        .filter(url => this.config.urls.some(cacheUrl => cacheUrl === url) ||\n                        this.patterns.some(pattern => pattern.test(url)))\n                        // Finally, process each resource in turn.\n                        .reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                        yield previous;\n                        const req = this.adapter.newRequest(url);\n                        // It's possible that the resource in question is already cached. If so,\n                        // continue to the next one.\n                        const alreadyCached = ((yield cache.match(req)) !== undefined);\n                        if (alreadyCached) {\n                            return;\n                        }\n                        // Get the most recent old version of the resource.\n                        const res = yield updateFrom.lookupResourceWithoutHash(url);\n                        if (res === null || res.metadata === undefined) {\n                            // Unexpected, but not harmful.\n                            return;\n                        }\n                        // Write it into the cache. It may already be expired, but it can still serve\n                        // traffic until it's updated (stale-while-revalidate approach).\n                        yield cache.put(req, res.response);\n                        yield metaTable.write(url, Object.assign(Object.assign({}, res.metadata), { used: false }));\n                    }), Promise.resolve());\n                }\n            });\n        }\n    }\n    class LazyAssetGroup extends AssetGroup {\n        initializeFully(updateFrom) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // No action necessary if no update source is available - resources managed in this group\n                // are all lazily loaded, so there's nothing to initialize.\n                if (updateFrom === undefined) {\n                    return;\n                }\n                // Open the cache which actually holds requests.\n                const cache = yield this.cache;\n                // Loop through the listed resources, caching any which are available.\n                yield this.config.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                    // Wait on all previous operations to complete.\n                    yield previous;\n                    // Construct the Request for this url.\n                    const req = this.adapter.newRequest(url);\n                    // First, check the cache to see if there is already a copy of this resource.\n                    const alreadyCached = (yield cache.match(req)) !== undefined;\n                    // If the resource is in the cache already, it can be skipped.\n                    if (alreadyCached) {\n                        return;\n                    }\n                    const updated = yield this.maybeUpdate(updateFrom, req, cache);\n                    if (this.config.updateMode === 'prefetch' && !updated) {\n                        // If the resource was not updated, either it was not cached before or\n                        // the previously cached version didn't match the updated hash. In that\n                        // case, prefetch update mode dictates that the resource will be updated,\n                        // except if it was not previously utilized. Check the status of the\n                        // cached resource to see.\n                        const cacheStatus = yield updateFrom.recentCacheStatus(url);\n                        // If the resource is not cached, or was cached but unused, then it will be\n                        // loaded lazily.\n                        if (cacheStatus !== UpdateCacheStatus.CACHED) {\n                            return;\n                        }\n                        // Update from the network.\n                        yield this.fetchAndCacheOnce(req, false);\n                    }\n                }), Promise.resolve());\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    /**\n     * Manages an instance of `LruState` and moves URLs to the head of the\n     * chain when requested.\n     */\n    class LruList {\n        constructor(state) {\n            if (state === undefined) {\n                state = {\n                    head: null,\n                    tail: null,\n                    map: {},\n                    count: 0,\n                };\n            }\n            this.state = state;\n        }\n        /**\n         * The current count of URLs in the list.\n         */\n        get size() { return this.state.count; }\n        /**\n         * Remove the tail.\n         */\n        pop() {\n            // If there is no tail, return null.\n            if (this.state.tail === null) {\n                return null;\n            }\n            const url = this.state.tail;\n            this.remove(url);\n            // This URL has been successfully evicted.\n            return url;\n        }\n        remove(url) {\n            const node = this.state.map[url];\n            if (node === undefined) {\n                return false;\n            }\n            // Special case if removing the current head.\n            if (this.state.head === url) {\n                // The node is the current head. Special case the removal.\n                if (node.next === null) {\n                    // This is the only node. Reset the cache to be empty.\n                    this.state.head = null;\n                    this.state.tail = null;\n                    this.state.map = {};\n                    this.state.count = 0;\n                    return true;\n                }\n                // There is at least one other node. Make the next node the new head.\n                const next = this.state.map[node.next];\n                next.previous = null;\n                this.state.head = next.url;\n                node.next = null;\n                delete this.state.map[url];\n                this.state.count--;\n                return true;\n            }\n            // The node is not the head, so it has a previous. It may or may not be the tail.\n            // If it is not, then it has a next. First, grab the previous node.\n            const previous = this.state.map[node.previous];\n            // Fix the forward pointer to skip over node and go directly to node.next.\n            previous.next = node.next;\n            // node.next may or may not be set. If it is, fix the back pointer to skip over node.\n            // If it's not set, then this node happened to be the tail, and the tail needs to be\n            // updated to point to the previous node (removing the tail).\n            if (node.next !== null) {\n                // There is a next node, fix its back pointer to skip this node.\n                this.state.map[node.next].previous = node.previous;\n            }\n            else {\n                // There is no next node - the accessed node must be the tail. Move the tail pointer.\n                this.state.tail = node.previous;\n            }\n            node.next = null;\n            node.previous = null;\n            delete this.state.map[url];\n            // Count the removal.\n            this.state.count--;\n            return true;\n        }\n        accessed(url) {\n            // When a URL is accessed, its node needs to be moved to the head of the chain.\n            // This is accomplished in two steps:\n            //\n            // 1) remove the node from its position within the chain.\n            // 2) insert the node as the new head.\n            //\n            // Sometimes, a URL is accessed which has not been seen before. In this case, step 1 can\n            // be skipped completely (which will grow the chain by one). Of course, if the node is\n            // already the head, this whole operation can be skipped.\n            if (this.state.head === url) {\n                // The URL is already in the head position, accessing it is a no-op.\n                return;\n            }\n            // Look up the node in the map, and construct a new entry if it's\n            const node = this.state.map[url] || { url, next: null, previous: null };\n            // Step 1: remove the node from its position within the chain, if it is in the chain.\n            if (this.state.map[url] !== undefined) {\n                this.remove(url);\n            }\n            // Step 2: insert the node at the head of the chain.\n            // First, check if there's an existing head node. If there is, it has previous: null.\n            // Its previous pointer should be set to the node we're inserting.\n            if (this.state.head !== null) {\n                this.state.map[this.state.head].previous = url;\n            }\n            // The next pointer of the node being inserted gets set to the old head, before the head\n            // pointer is updated to this node.\n            node.next = this.state.head;\n            // The new head is the new node.\n            this.state.head = url;\n            // If there is no tail, then this is the first node, and is both the head and the tail.\n            if (this.state.tail === null) {\n                this.state.tail = url;\n            }\n            // Set the node in the map of nodes (if the URL has been seen before, this is a no-op)\n            // and count the insertion.\n            this.state.map[url] = node;\n            this.state.count++;\n        }\n    }\n    /**\n     * A group of cached resources determined by a set of URL patterns which follow a LRU policy\n     * for caching.\n     */\n    class DataGroup {\n        constructor(scope, adapter, config, db, debugHandler, prefix) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.config = config;\n            this.db = db;\n            this.debugHandler = debugHandler;\n            this.prefix = prefix;\n            /**\n             * Tracks the LRU state of resources in this cache.\n             */\n            this._lru = null;\n            this.patterns = this.config.patterns.map(pattern => new RegExp(pattern));\n            this.cache = this.scope.caches.open(`${this.prefix}:dynamic:${this.config.name}:cache`);\n            this.lruTable = this.db.open(`${this.prefix}:dynamic:${this.config.name}:lru`);\n            this.ageTable = this.db.open(`${this.prefix}:dynamic:${this.config.name}:age`);\n        }\n        /**\n         * Lazily initialize/load the LRU chain.\n         */\n        lru() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                if (this._lru === null) {\n                    const table = yield this.lruTable;\n                    try {\n                        this._lru = new LruList(yield table.read('lru'));\n                    }\n                    catch (_a) {\n                        this._lru = new LruList();\n                    }\n                }\n                return this._lru;\n            });\n        }\n        /**\n         * Sync the LRU chain to non-volatile storage.\n         */\n        syncLru() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                if (this._lru === null) {\n                    return;\n                }\n                const table = yield this.lruTable;\n                try {\n                    return table.write('lru', this._lru.state);\n                }\n                catch (err) {\n                    // Writing lru cache table failed. This could be a result of a full storage.\n                    // Continue serving clients as usual.\n                    this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).syncLru()`);\n                    // TODO: Better detect/handle full storage; e.g. using\n                    // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).\n                }\n            });\n        }\n        /**\n         * Process a fetch event and return a `Response` if the resource is covered by this group,\n         * or `null` otherwise.\n         */\n        handleFetch(req, ctx) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Do nothing\n                if (!this.patterns.some(pattern => pattern.test(req.url))) {\n                    return null;\n                }\n                // Lazily initialize the LRU cache.\n                const lru = yield this.lru();\n                // The URL matches this cache. First, check whether this is a mutating request or not.\n                switch (req.method) {\n                    case 'OPTIONS':\n                        // Don't try to cache this - it's non-mutating, but is part of a mutating request.\n                        // Most likely SWs don't even see this, but this guard is here just in case.\n                        return null;\n                    case 'GET':\n                    case 'HEAD':\n                        // Handle the request with whatever strategy was selected.\n                        switch (this.config.strategy) {\n                            case 'freshness':\n                                return this.handleFetchWithFreshness(req, ctx, lru);\n                            case 'performance':\n                                return this.handleFetchWithPerformance(req, ctx, lru);\n                            default:\n                                throw new Error(`Unknown strategy: ${this.config.strategy}`);\n                        }\n                    default:\n                        // This was a mutating request. Assume the cache for this URL is no longer valid.\n                        const wasCached = lru.remove(req.url);\n                        // If there was a cached entry, remove it.\n                        if (wasCached) {\n                            yield this.clearCacheForUrl(req.url);\n                        }\n                        // Sync the LRU chain to non-volatile storage.\n                        yield this.syncLru();\n                        // Finally, fall back on the network.\n                        return this.safeFetch(req);\n                }\n            });\n        }\n        handleFetchWithPerformance(req, ctx, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                let res = null;\n                // Check the cache first. If the resource exists there (and is not expired), the cached\n                // version can be used.\n                const fromCache = yield this.loadFromCache(req, lru);\n                if (fromCache !== null) {\n                    res = fromCache.res;\n                    // Check the age of the resource.\n                    if (this.config.refreshAheadMs !== undefined && fromCache.age >= this.config.refreshAheadMs) {\n                        ctx.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru));\n                    }\n                }\n                if (res !== null) {\n                    return res;\n                }\n                // No match from the cache. Go to the network. Note that this is not an 'await'\n                // call, networkFetch is the actual Promise. This is due to timeout handling.\n                const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);\n                res = yield timeoutFetch;\n                // Since fetch() will always return a response, undefined indicates a timeout.\n                if (res === undefined) {\n                    // The request timed out. Return a Gateway Timeout error.\n                    res = this.adapter.newResponse(null, { status: 504, statusText: 'Gateway Timeout' });\n                    // Cache the network response eventually.\n                    ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru));\n                }\n                else {\n                    // The request completed in time, so cache it inline with the response flow.\n                    yield this.safeCacheResponse(req, res, lru);\n                }\n                return res;\n            });\n        }\n        handleFetchWithFreshness(req, ctx, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Start with a network fetch.\n                const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);\n                let res;\n                // If that fetch errors, treat it as a timed out request.\n                try {\n                    res = yield timeoutFetch;\n                }\n                catch (_a) {\n                    res = undefined;\n                }\n                // If the network fetch times out or errors, fall back on the cache.\n                if (res === undefined) {\n                    ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true));\n                    // Ignore the age, the network response will be cached anyway due to the\n                    // behavior of freshness.\n                    const fromCache = yield this.loadFromCache(req, lru);\n                    res = (fromCache !== null) ? fromCache.res : null;\n                }\n                else {\n                    yield this.safeCacheResponse(req, res, lru, true);\n                }\n                // Either the network fetch didn't time out, or the cache yielded a usable response.\n                // In either case, use it.\n                if (res !== null) {\n                    return res;\n                }\n                // No response in the cache. No choice but to fall back on the full network fetch.\n                return networkFetch;\n            });\n        }\n        networkFetchWithTimeout(req) {\n            // If there is a timeout configured, race a timeout Promise with the network fetch.\n            // Otherwise, just fetch from the network directly.\n            if (this.config.timeoutMs !== undefined) {\n                const networkFetch = this.scope.fetch(req);\n                const safeNetworkFetch = (() => __awaiter$1(this, void 0, void 0, function* () {\n                    try {\n                        return yield networkFetch;\n                    }\n                    catch (_a) {\n                        return this.adapter.newResponse(null, {\n                            status: 504,\n                            statusText: 'Gateway Timeout',\n                        });\n                    }\n                }))();\n                const networkFetchUndefinedError = (() => __awaiter$1(this, void 0, void 0, function* () {\n                    try {\n                        return yield networkFetch;\n                    }\n                    catch (_b) {\n                        return undefined;\n                    }\n                }))();\n                // Construct a Promise<undefined> for the timeout.\n                const timeout = this.adapter.timeout(this.config.timeoutMs);\n                // Race that with the network fetch. This will either be a Response, or `undefined`\n                // in the event that the request errored or timed out.\n                return [Promise.race([networkFetchUndefinedError, timeout]), safeNetworkFetch];\n            }\n            else {\n                const networkFetch = this.safeFetch(req);\n                // Do a plain fetch.\n                return [networkFetch, networkFetch];\n            }\n        }\n        safeCacheResponse(req, resOrPromise, lru, okToCacheOpaque) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                try {\n                    const res = yield resOrPromise;\n                    try {\n                        yield this.cacheResponse(req, res, lru, okToCacheOpaque);\n                    }\n                    catch (err) {\n                        // Saving the API response failed. This could be a result of a full storage.\n                        // Since this data is cached lazily and temporarily, continue serving clients as usual.\n                        this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).safeCacheResponse(${req.url}, status: ${res.status})`);\n                        // TODO: Better detect/handle full storage; e.g. using\n                        // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).\n                    }\n                }\n                catch (_a) {\n                    // Request failed\n                    // TODO: Handle this error somehow?\n                }\n            });\n        }\n        loadFromCache(req, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Look for a response in the cache. If one exists, return it.\n                const cache = yield this.cache;\n                let res = yield cache.match(req);\n                if (res !== undefined) {\n                    // A response was found in the cache, but its age is not yet known. Look it up.\n                    try {\n                        const ageTable = yield this.ageTable;\n                        const age = this.adapter.time - (yield ageTable.read(req.url)).age;\n                        // If the response is young enough, use it.\n                        if (age <= this.config.maxAge) {\n                            // Successful match from the cache. Use the response, after marking it as having\n                            // been accessed.\n                            lru.accessed(req.url);\n                            return { res, age };\n                        }\n                        // Otherwise, or if there was an error, assume the response is expired, and evict it.\n                    }\n                    catch (_a) {\n                        // Some error getting the age for the response. Assume it's expired.\n                    }\n                    lru.remove(req.url);\n                    yield this.clearCacheForUrl(req.url);\n                    // TODO: avoid duplicate in event of network timeout, maybe.\n                    yield this.syncLru();\n                }\n                return null;\n            });\n        }\n        /**\n         * Operation for caching the response from the server. This has to happen all\n         * at once, so that the cache and LRU tracking remain in sync. If the network request\n         * completes before the timeout, this logic will be run inline with the response flow.\n         * If the request times out on the server, an error will be returned but the real network\n         * request will still be running in the background, to be cached when it completes.\n         */\n        cacheResponse(req, res, lru, okToCacheOpaque = false) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Only cache successful responses.\n                if (!(res.ok || (okToCacheOpaque && res.type === 'opaque'))) {\n                    return;\n                }\n                // If caching this response would make the cache exceed its maximum size, evict something\n                // first.\n                if (lru.size >= this.config.maxSize) {\n                    // The cache is too big, evict something.\n                    const evictedUrl = lru.pop();\n                    if (evictedUrl !== null) {\n                        yield this.clearCacheForUrl(evictedUrl);\n                    }\n                }\n                // TODO: evaluate for possible race conditions during flaky network periods.\n                // Mark this resource as having been accessed recently. This ensures it won't be evicted\n                // until enough other resources are requested that it falls off the end of the LRU chain.\n                lru.accessed(req.url);\n                // Store the response in the cache (cloning because the browser will consume\n                // the body during the caching operation).\n                yield (yield this.cache).put(req, res.clone());\n                // Store the age of the cache.\n                const ageTable = yield this.ageTable;\n                yield ageTable.write(req.url, { age: this.adapter.time });\n                // Sync the LRU chain to non-volatile storage.\n                yield this.syncLru();\n            });\n        }\n        /**\n         * Delete all of the saved state which this group uses to track resources.\n         */\n        cleanup() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Remove both the cache and the database entries which track LRU stats.\n                yield Promise.all([\n                    this.scope.caches.delete(`${this.prefix}:dynamic:${this.config.name}:cache`),\n                    this.db.delete(`${this.prefix}:dynamic:${this.config.name}:age`),\n                    this.db.delete(`${this.prefix}:dynamic:${this.config.name}:lru`),\n                ]);\n            });\n        }\n        /**\n         * Clear the state of the cache for a particular resource.\n         *\n         * This doesn't remove the resource from the LRU table, that is assumed to have\n         * been done already. This clears the GET and HEAD versions of the request from\n         * the cache itself, as well as the metadata stored in the age table.\n         */\n        clearCacheForUrl(url) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                const [cache, ageTable] = yield Promise.all([this.cache, this.ageTable]);\n                yield Promise.all([\n                    cache.delete(this.adapter.newRequest(url, { method: 'GET' })),\n                    cache.delete(this.adapter.newRequest(url, { method: 'HEAD' })),\n                    ageTable.delete(url),\n                ]);\n            });\n        }\n        safeFetch(req) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                try {\n                    return this.scope.fetch(req);\n                }\n                catch (_a) {\n                    return this.adapter.newResponse(null, {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const BACKWARDS_COMPATIBILITY_NAVIGATION_URLS = [\n        { positive: true, regex: '^/.*$' },\n        { positive: false, regex: '^/.*\\\\.[^/]*$' },\n        { positive: false, regex: '^/.*__' },\n    ];\n    /**\n     * A specific version of the application, identified by a unique manifest\n     * as determined by its hash.\n     *\n     * Each `AppVersion` can be thought of as a published version of the app\n     * that can be installed as an update to any previously installed versions.\n     */\n    class AppVersion {\n        constructor(scope, adapter, database, idle, debugHandler, manifest, manifestHash) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.database = database;\n            this.idle = idle;\n            this.debugHandler = debugHandler;\n            this.manifest = manifest;\n            this.manifestHash = manifestHash;\n            /**\n             * A Map of absolute URL paths (/foo.txt) to the known hash of their\n             * contents (if available).\n             */\n            this.hashTable = new Map();\n            /**\n             * Tracks whether the manifest has encountered any inconsistencies.\n             */\n            this._okay = true;\n            // The hashTable within the manifest is an Object - convert it to a Map for easier lookups.\n            Object.keys(this.manifest.hashTable).forEach(url => {\n                this.hashTable.set(url, this.manifest.hashTable[url]);\n            });\n            // Process each `AssetGroup` declared in the manifest. Each declared group gets an `AssetGroup`\n            // instance\n            // created for it, of a type that depends on the configuration mode.\n            this.assetGroups = (manifest.assetGroups || []).map(config => {\n                // Every asset group has a cache that's prefixed by the manifest hash and the name of the\n                // group.\n                const prefix = `${adapter.cacheNamePrefix}:${this.manifestHash}:assets`;\n                // Check the caching mode, which determines when resources will be fetched/updated.\n                switch (config.installMode) {\n                    case 'prefetch':\n                        return new PrefetchAssetGroup(this.scope, this.adapter, this.idle, config, this.hashTable, this.database, prefix);\n                    case 'lazy':\n                        return new LazyAssetGroup(this.scope, this.adapter, this.idle, config, this.hashTable, this.database, prefix);\n                }\n            });\n            // Process each `DataGroup` declared in the manifest.\n            this.dataGroups =\n                (manifest.dataGroups || [])\n                    .map(config => new DataGroup(this.scope, this.adapter, config, this.database, this.debugHandler, `${adapter.cacheNamePrefix}:${config.version}:data`));\n            // This keeps backwards compatibility with app versions without navigation urls.\n            // Fix: https://github.com/angular/angular/issues/27209\n            manifest.navigationUrls = manifest.navigationUrls || BACKWARDS_COMPATIBILITY_NAVIGATION_URLS;\n            // Create `include`/`exclude` RegExps for the `navigationUrls` declared in the manifest.\n            const includeUrls = manifest.navigationUrls.filter(spec => spec.positive);\n            const excludeUrls = manifest.navigationUrls.filter(spec => !spec.positive);\n            this.navigationUrls = {\n                include: includeUrls.map(spec => new RegExp(spec.regex)),\n                exclude: excludeUrls.map(spec => new RegExp(spec.regex)),\n            };\n        }\n        get okay() { return this._okay; }\n        /**\n         * Fully initialize this version of the application. If this Promise resolves successfully, all\n         * required\n         * data has been safely downloaded.\n         */\n        initializeFully(updateFrom) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                try {\n                    // Fully initialize each asset group, in series. Starts with an empty Promise,\n                    // and waits for the previous groups to have been initialized before initializing\n                    // the next one in turn.\n                    yield this.assetGroups.reduce((previous, group) => __awaiter$2(this, void 0, void 0, function* () {\n                        // Wait for the previous groups to complete initialization. If there is a\n                        // failure, this will throw, and each subsequent group will throw, until the\n                        // whole sequence fails.\n                        yield previous;\n                        // Initialize this group.\n                        return group.initializeFully(updateFrom);\n                    }), Promise.resolve());\n                }\n                catch (err) {\n                    this._okay = false;\n                    throw err;\n                }\n            });\n        }\n        handleFetch(req, context) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                // Check the request against each `AssetGroup` in sequence. If an `AssetGroup` can't handle the\n                // request,\n                // it will return `null`. Thus, the first non-null response is the SW's answer to the request.\n                // So reduce\n                // the group list, keeping track of a possible response. If there is one, it gets passed\n                // through, and if\n                // not the next group is consulted to produce a candidate response.\n                const asset = yield this.assetGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    // Wait on the previous potential response. If it's not null, it should just be passed\n                    // through.\n                    const resp = yield potentialResponse;\n                    if (resp !== null) {\n                        return resp;\n                    }\n                    // No response has been found yet. Maybe this group will have one.\n                    return group.handleFetch(req, context);\n                }), Promise.resolve(null));\n                // The result of the above is the asset response, if there is any, or null otherwise. Return the\n                // asset\n                // response if there was one. If not, check with the data caching groups.\n                if (asset !== null) {\n                    return asset;\n                }\n                // Perform the same reduction operation as above, but this time processing\n                // the data caching groups.\n                const data = yield this.dataGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    const resp = yield potentialResponse;\n                    if (resp !== null) {\n                        return resp;\n                    }\n                    return group.handleFetch(req, context);\n                }), Promise.resolve(null));\n                // If the data caching group returned a response, go with it.\n                if (data !== null) {\n                    return data;\n                }\n                // Next, check if this is a navigation request for a route. Detect circular\n                // navigations by checking if the request URL is the same as the index URL.\n                if (req.url !== this.manifest.index && this.isNavigationRequest(req)) {\n                    // This was a navigation request. Re-enter `handleFetch` with a request for\n                    // the URL.\n                    return this.handleFetch(this.adapter.newRequest(this.manifest.index), context);\n                }\n                return null;\n            });\n        }\n        /**\n         * Determine whether the request is a navigation request.\n         * Takes into account: Request mode, `Accept` header, `navigationUrls` patterns.\n         */\n        isNavigationRequest(req) {\n            if (req.mode !== 'navigate') {\n                return false;\n            }\n            if (!this.acceptsTextHtml(req)) {\n                return false;\n            }\n            const urlPrefix = this.scope.registration.scope.replace(/\\/$/, '');\n            const url = req.url.startsWith(urlPrefix) ? req.url.substr(urlPrefix.length) : req.url;\n            const urlWithoutQueryOrHash = url.replace(/[?#].*$/, '');\n            return this.navigationUrls.include.some(regex => regex.test(urlWithoutQueryOrHash)) &&\n                !this.navigationUrls.exclude.some(regex => regex.test(urlWithoutQueryOrHash));\n        }\n        /**\n         * Check this version for a given resource with a particular hash.\n         */\n        lookupResourceWithHash(url, hash) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                // Verify that this version has the requested resource cached. If not,\n                // there's no point in trying.\n                if (!this.hashTable.has(url)) {\n                    return null;\n                }\n                // Next, check whether the resource has the correct hash. If not, any cached\n                // response isn't usable.\n                if (this.hashTable.get(url) !== hash) {\n                    return null;\n                }\n                const cacheState = yield this.lookupResourceWithoutHash(url);\n                return cacheState && cacheState.response;\n            });\n        }\n        /**\n         * Check this version for a given resource regardless of its hash.\n         */\n        lookupResourceWithoutHash(url) {\n            // Limit the search to asset groups, and only scan the cache, don't\n            // load resources from the network.\n            return this.assetGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                const resp = yield potentialResponse;\n                if (resp !== null) {\n                    return resp;\n                }\n                // fetchFromCacheOnly() avoids any network fetches, and returns the\n                // full set of cache data, not just the Response.\n                return group.fetchFromCacheOnly(url);\n            }), Promise.resolve(null));\n        }\n        /**\n         * List all unhashed resources from all asset groups.\n         */\n        previouslyCachedResources() {\n            return this.assetGroups.reduce((resources, group) => __awaiter$2(this, void 0, void 0, function* () {\n                return (yield resources).concat(yield group.unhashedResources());\n            }), Promise.resolve([]));\n        }\n        recentCacheStatus(url) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                return this.assetGroups.reduce((current, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    const status = yield current;\n                    if (status === UpdateCacheStatus.CACHED) {\n                        return status;\n                    }\n                    const groupStatus = yield group.cacheStatus(url);\n                    if (groupStatus === UpdateCacheStatus.NOT_CACHED) {\n                        return status;\n                    }\n                    return groupStatus;\n                }), Promise.resolve(UpdateCacheStatus.NOT_CACHED));\n            });\n        }\n        /**\n         * Erase this application version, by cleaning up all the caches.\n         */\n        cleanup() {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                yield Promise.all(this.assetGroups.map(group => group.cleanup()));\n                yield Promise.all(this.dataGroups.map(group => group.cleanup()));\n            });\n        }\n        /**\n         * Get the opaque application data which was provided with the manifest.\n         */\n        get appData() { return this.manifest.appData || null; }\n        /**\n         * Check whether a request accepts `text/html` (based on the `Accept` header).\n         */\n        acceptsTextHtml(req) {\n            const accept = req.headers.get('Accept');\n            if (accept === null) {\n                return false;\n            }\n            const values = accept.split(',');\n            return values.some(value => value.trim().toLowerCase() === 'text/html');\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$3 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const DEBUG_LOG_BUFFER_SIZE = 100;\n    class DebugHandler {\n        constructor(driver, adapter) {\n            this.driver = driver;\n            this.adapter = adapter;\n            // There are two debug log message arrays. debugLogA records new debugging messages.\n            // Once it reaches DEBUG_LOG_BUFFER_SIZE, the array is moved to debugLogB and a new\n            // array is assigned to debugLogA. This ensures that insertion to the debug log is\n            // always O(1) no matter the number of logged messages, and that the total number\n            // of messages in the log never exceeds 2 * DEBUG_LOG_BUFFER_SIZE.\n            this.debugLogA = [];\n            this.debugLogB = [];\n        }\n        handleFetch(req) {\n            return __awaiter$3(this, void 0, void 0, function* () {\n                const [state, versions, idle] = yield Promise.all([\n                    this.driver.debugState(),\n                    this.driver.debugVersions(),\n                    this.driver.debugIdleState(),\n                ]);\n                const msgState = `NGSW Debug Info:\n\nDriver state: ${state.state} (${state.why})\nLatest manifest hash: ${state.latestHash || 'none'}\nLast update check: ${this.since(state.lastUpdateCheck)}`;\n                const msgVersions = versions\n                    .map(version => `=== Version ${version.hash} ===\n\nClients: ${version.clients.join(', ')}`)\n                    .join('\\n\\n');\n                const msgIdle = `=== Idle Task Queue ===\nLast update tick: ${this.since(idle.lastTrigger)}\nLast update run: ${this.since(idle.lastRun)}\nTask queue:\n${idle.queue.map(v => ' * ' + v).join('\\n')}\n\nDebug log:\n${this.formatDebugLog(this.debugLogB)}\n${this.formatDebugLog(this.debugLogA)}\n`;\n                return this.adapter.newResponse(`${msgState}\n\n${msgVersions}\n\n${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }) });\n            });\n        }\n        since(time) {\n            if (time === null) {\n                return 'never';\n            }\n            let age = this.adapter.time - time;\n            const days = Math.floor(age / 86400000);\n            age = age % 86400000;\n            const hours = Math.floor(age / 3600000);\n            age = age % 3600000;\n            const minutes = Math.floor(age / 60000);\n            age = age % 60000;\n            const seconds = Math.floor(age / 1000);\n            const millis = age % 1000;\n            return '' + (days > 0 ? `${days}d` : '') + (hours > 0 ? `${hours}h` : '') +\n                (minutes > 0 ? `${minutes}m` : '') + (seconds > 0 ? `${seconds}s` : '') +\n                (millis > 0 ? `${millis}u` : '');\n        }\n        log(value, context = '') {\n            // Rotate the buffers if debugLogA has grown too large.\n            if (this.debugLogA.length === DEBUG_LOG_BUFFER_SIZE) {\n                this.debugLogB = this.debugLogA;\n                this.debugLogA = [];\n            }\n            // Convert errors to string for logging.\n            if (typeof value !== 'string') {\n                value = this.errorToString(value);\n            }\n            // Log the message.\n            this.debugLogA.push({ value, time: this.adapter.time, context });\n        }\n        errorToString(err) { return `${err.name}(${err.message}, ${err.stack})`; }\n        formatDebugLog(log) {\n            return log.map(entry => `[${this.since(entry.time)}] ${entry.value} ${entry.context}`)\n                .join('\\n');\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$4 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    class IdleScheduler {\n        constructor(adapter, threshold, debug) {\n            this.adapter = adapter;\n            this.threshold = threshold;\n            this.debug = debug;\n            this.queue = [];\n            this.scheduled = null;\n            this.empty = Promise.resolve();\n            this.emptyResolve = null;\n            this.lastTrigger = null;\n            this.lastRun = null;\n        }\n        trigger() {\n            return __awaiter$4(this, void 0, void 0, function* () {\n                this.lastTrigger = this.adapter.time;\n                if (this.queue.length === 0) {\n                    return;\n                }\n                if (this.scheduled !== null) {\n                    this.scheduled.cancel = true;\n                }\n                const scheduled = {\n                    cancel: false,\n                };\n                this.scheduled = scheduled;\n                yield this.adapter.timeout(this.threshold);\n                if (scheduled.cancel) {\n                    return;\n                }\n                this.scheduled = null;\n                yield this.execute();\n            });\n        }\n        execute() {\n            return __awaiter$4(this, void 0, void 0, function* () {\n                this.lastRun = this.adapter.time;\n                while (this.queue.length > 0) {\n                    const queue = this.queue;\n                    this.queue = [];\n                    yield queue.reduce((previous, task) => __awaiter$4(this, void 0, void 0, function* () {\n                        yield previous;\n                        try {\n                            yield task.run();\n                        }\n                        catch (err) {\n                            this.debug.log(err, `while running idle task ${task.desc}`);\n                        }\n                    }), Promise.resolve());\n                }\n                if (this.emptyResolve !== null) {\n                    this.emptyResolve();\n                    this.emptyResolve = null;\n                }\n                this.empty = Promise.resolve();\n            });\n        }\n        schedule(desc, run) {\n            this.queue.push({ desc, run });\n            if (this.emptyResolve === null) {\n                this.empty = new Promise(resolve => { this.emptyResolve = resolve; });\n            }\n        }\n        get size() { return this.queue.length; }\n        get taskDescriptions() { return this.queue.map(task => task.desc); }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    function hashManifest(manifest) {\n        return sha1(JSON.stringify(manifest));\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    function isMsgCheckForUpdates(msg) {\n        return msg.action === 'CHECK_FOR_UPDATES';\n    }\n    function isMsgActivateUpdate(msg) {\n        return msg.action === 'ACTIVATE_UPDATE';\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$5 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const IDLE_THRESHOLD = 5000;\n    const SUPPORTED_CONFIG_VERSION = 1;\n    const NOTIFICATION_OPTION_NAMES = [\n        'actions', 'badge', 'body', 'data', 'dir', 'icon', 'image', 'lang', 'renotify',\n        'requireInteraction', 'silent', 'tag', 'timestamp', 'title', 'vibrate'\n    ];\n    var DriverReadyState;\n    (function (DriverReadyState) {\n        // The SW is operating in a normal mode, responding to all traffic.\n        DriverReadyState[DriverReadyState[\"NORMAL\"] = 0] = \"NORMAL\";\n        // The SW does not have a clean installation of the latest version of the app, but older\n        // cached versions are safe to use so long as they don't try to fetch new dependencies.\n        // This is a degraded state.\n        DriverReadyState[DriverReadyState[\"EXISTING_CLIENTS_ONLY\"] = 1] = \"EXISTING_CLIENTS_ONLY\";\n        // The SW has decided that caching is completely unreliable, and is forgoing request\n        // handling until the next restart.\n        DriverReadyState[DriverReadyState[\"SAFE_MODE\"] = 2] = \"SAFE_MODE\";\n    })(DriverReadyState || (DriverReadyState = {}));\n    class Driver {\n        constructor(scope, adapter, db) {\n            // Set up all the event handlers that the SW needs.\n            this.scope = scope;\n            this.adapter = adapter;\n            this.db = db;\n            /**\n             * Tracks the current readiness condition under which the SW is operating. This controls\n             * whether the SW attempts to respond to some or all requests.\n             */\n            this.state = DriverReadyState.NORMAL;\n            this.stateMessage = '(nominal)';\n            /**\n             * Tracks whether the SW is in an initialized state or not. Before initialization,\n             * it's not legal to respond to requests.\n             */\n            this.initialized = null;\n            /**\n             * Maps client IDs to the manifest hash of the application version being used to serve\n             * them. If a client ID is not present here, it has not yet been assigned a version.\n             *\n             * If a ManifestHash appears here, it is also present in the `versions` map below.\n             */\n            this.clientVersionMap = new Map();\n            /**\n             * Maps manifest hashes to instances of `AppVersion` for those manifests.\n             */\n            this.versions = new Map();\n            /**\n             * The latest version fetched from the server.\n             *\n             * Valid after initialization has completed.\n             */\n            this.latestHash = null;\n            this.lastUpdateCheck = null;\n            /**\n             * Whether there is a check for updates currently scheduled due to navigation.\n             */\n            this.scheduledNavUpdateCheck = false;\n            /**\n             * Keep track of whether we have logged an invalid `only-if-cached` request.\n             * (See `.onFetch()` for details.)\n             */\n            this.loggedInvalidOnlyIfCachedRequest = false;\n            // The install event is triggered when the service worker is first installed.\n            this.scope.addEventListener('install', (event) => {\n                // SW code updates are separate from application updates, so code updates are\n                // almost as straightforward as restarting the SW. Because of this, it's always\n                // safe to skip waiting until application tabs are closed, and activate the new\n                // SW version immediately.\n                event.waitUntil(this.scope.skipWaiting());\n            });\n            // The activate event is triggered when this version of the service worker is\n            // first activated.\n            this.scope.addEventListener('activate', (event) => {\n                event.waitUntil((() => __awaiter$5(this, void 0, void 0, function* () {\n                    // As above, it's safe to take over from existing clients immediately, since the new SW\n                    // version will continue to serve the old application.\n                    yield this.scope.clients.claim();\n                    // Once all clients have been taken over, we can delete caches used by old versions of\n                    // `@angular/service-worker`, which are no longer needed. This can happen in the background.\n                    this.idle.schedule('activate: cleanup-old-sw-caches', () => __awaiter$5(this, void 0, void 0, function* () {\n                        try {\n                            yield this.cleanupOldSwCaches();\n                        }\n                        catch (err) {\n                            // Nothing to do - cleanup failed. Just log it.\n                            this.debugger.log(err, 'cleanupOldSwCaches @ activate: cleanup-old-sw-caches');\n                        }\n                    }));\n                }))());\n                // Rather than wait for the first fetch event, which may not arrive until\n                // the next time the application is loaded, the SW takes advantage of the\n                // activation event to schedule initialization. However, if this were run\n                // in the context of the 'activate' event, waitUntil() here would cause fetch\n                // events to block until initialization completed. Thus, the SW does a\n                // postMessage() to itself, to schedule a new event loop iteration with an\n                // entirely separate event context. The SW will be kept alive by waitUntil()\n                // within that separate context while initialization proceeds, while at the\n                // same time the activation event is allowed to resolve and traffic starts\n                // being served.\n                if (this.scope.registration.active !== null) {\n                    this.scope.registration.active.postMessage({ action: 'INITIALIZE' });\n                }\n            });\n            // Handle the fetch, message, and push events.\n            this.scope.addEventListener('fetch', (event) => this.onFetch(event));\n            this.scope.addEventListener('message', (event) => this.onMessage(event));\n            this.scope.addEventListener('push', (event) => this.onPush(event));\n            this.scope.addEventListener('notificationclick', (event) => this.onClick(event));\n            // The debugger generates debug pages in response to debugging requests.\n            this.debugger = new DebugHandler(this, this.adapter);\n            // The IdleScheduler will execute idle tasks after a given delay.\n            this.idle = new IdleScheduler(this.adapter, IDLE_THRESHOLD, this.debugger);\n        }\n        /**\n         * The handler for fetch events.\n         *\n         * This is the transition point between the synchronous event handler and the\n         * asynchronous execution that eventually resolves for respondWith() and waitUntil().\n         */\n        onFetch(event) {\n            const req = event.request;\n            const scopeUrl = this.scope.registration.scope;\n            const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);\n            if (req.headers.has('ngsw-bypass') || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) {\n                return;\n            }\n            // The only thing that is served unconditionally is the debug page.\n            if (requestUrlObj.path === '/ngsw/state') {\n                // Allow the debugger to handle the request, but don't affect SW state in any other way.\n                event.respondWith(this.debugger.handleFetch(req));\n                return;\n            }\n            // If the SW is in a broken state where it's not safe to handle requests at all,\n            // returning causes the request to fall back on the network. This is preferred over\n            // `respondWith(fetch(req))` because the latter still shows in DevTools that the\n            // request was handled by the SW.\n            if (this.state === DriverReadyState.SAFE_MODE) {\n                // Even though the worker is in safe mode, idle tasks still need to happen so\n                // things like update checks, etc. can take place.\n                event.waitUntil(this.idle.trigger());\n                return;\n            }\n            // Although \"passive mixed content\" (like images) only produces a warning without a\n            // ServiceWorker, fetching it via a ServiceWorker results in an error. Let such requests be\n            // handled by the browser, since handling with the ServiceWorker would fail anyway.\n            // See https://github.com/angular/angular/issues/23012#issuecomment-376430187 for more details.\n            if (requestUrlObj.origin.startsWith('http:') && scopeUrl.startsWith('https:')) {\n                // Still, log the incident for debugging purposes.\n                this.debugger.log(`Ignoring passive mixed content request: Driver.fetch(${req.url})`);\n                return;\n            }\n            // When opening DevTools in Chrome, a request is made for the current URL (and possibly related\n            // resources, e.g. scripts) with `cache: 'only-if-cached'` and `mode: 'no-cors'`. These request\n            // will eventually fail, because `only-if-cached` is only allowed to be used with\n            // `mode: 'same-origin'`.\n            // This is likely a bug in Chrome DevTools. Avoid handling such requests.\n            // (See also https://github.com/angular/angular/issues/22362.)\n            // TODO(gkalpak): Remove once no longer necessary (i.e. fixed in Chrome DevTools).\n            if (req.cache === 'only-if-cached' && req.mode !== 'same-origin') {\n                // Log the incident only the first time it happens, to avoid spamming the logs.\n                if (!this.loggedInvalidOnlyIfCachedRequest) {\n                    this.loggedInvalidOnlyIfCachedRequest = true;\n                    this.debugger.log(`Ignoring invalid request: 'only-if-cached' can be set only with 'same-origin' mode`, `Driver.fetch(${req.url}, cache: ${req.cache}, mode: ${req.mode})`);\n                }\n                return;\n            }\n            // Past this point, the SW commits to handling the request itself. This could still\n            // fail (and result in `state` being set to `SAFE_MODE`), but even in that case the\n            // SW will still deliver a response.\n            event.respondWith(this.handleFetch(event));\n        }\n        /**\n         * The handler for message events.\n         */\n        onMessage(event) {\n            // Ignore message events when the SW is in safe mode, for now.\n            if (this.state === DriverReadyState.SAFE_MODE) {\n                return;\n            }\n            // If the message doesn't have the expected signature, ignore it.\n            const data = event.data;\n            if (!data || !data.action) {\n                return;\n            }\n            event.waitUntil((() => __awaiter$5(this, void 0, void 0, function* () {\n                // Initialization is the only event which is sent directly from the SW to itself, and thus\n                // `event.source` is not a `Client`. Handle it here, before the check for `Client` sources.\n                if (data.action === 'INITIALIZE') {\n                    return this.ensureInitialized(event);\n                }\n                // Only messages from true clients are accepted past this point.\n                // This is essentially a typecast.\n                if (!this.adapter.isClient(event.source)) {\n                    return;\n                }\n                // Handle the message and keep the SW alive until it's handled.\n                yield this.ensureInitialized(event);\n                yield this.handleMessage(data, event.source);\n            }))());\n        }\n        onPush(msg) {\n            // Push notifications without data have no effect.\n            if (!msg.data) {\n                return;\n            }\n            // Handle the push and keep the SW alive until it's handled.\n            msg.waitUntil(this.handlePush(msg.data.json()));\n        }\n        onClick(event) {\n            // Handle the click event and keep the SW alive until it's handled.\n            event.waitUntil(this.handleClick(event.notification, event.action));\n        }\n        ensureInitialized(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Since the SW may have just been started, it may or may not have been initialized already.\n                // `this.initialized` will be `null` if initialization has not yet been attempted, or will be a\n                // `Promise` which will resolve (successfully or unsuccessfully) if it has.\n                if (this.initialized !== null) {\n                    return this.initialized;\n                }\n                // Initialization has not yet been attempted, so attempt it. This should only ever happen once\n                // per SW instantiation.\n                try {\n                    this.initialized = this.initialize();\n                    yield this.initialized;\n                }\n                catch (error) {\n                    // If initialization fails, the SW needs to enter a safe state, where it declines to respond\n                    // to network requests.\n                    this.state = DriverReadyState.SAFE_MODE;\n                    this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`;\n                    throw error;\n                }\n                finally {\n                    // Regardless if initialization succeeded, background tasks still need to happen.\n                    event.waitUntil(this.idle.trigger());\n                }\n            });\n        }\n        handleMessage(msg, from) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                if (isMsgCheckForUpdates(msg)) {\n                    const action = (() => __awaiter$5(this, void 0, void 0, function* () { yield this.checkForUpdate(); }))();\n                    yield this.reportStatus(from, action, msg.statusNonce);\n                }\n                else if (isMsgActivateUpdate(msg)) {\n                    yield this.reportStatus(from, this.updateClient(from), msg.statusNonce);\n                }\n            });\n        }\n        handlePush(data) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.broadcast({\n                    type: 'PUSH',\n                    data,\n                });\n                if (!data.notification || !data.notification.title) {\n                    return;\n                }\n                const desc = data.notification;\n                let options = {};\n                NOTIFICATION_OPTION_NAMES.filter(name => desc.hasOwnProperty(name))\n                    .forEach(name => options[name] = desc[name]);\n                yield this.scope.registration.showNotification(desc['title'], options);\n            });\n        }\n        handleClick(notification, action) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                notification.close();\n                const options = {};\n                // The filter uses `name in notification` because the properties are on the prototype so\n                // hasOwnProperty does not work here\n                NOTIFICATION_OPTION_NAMES.filter(name => name in notification)\n                    .forEach(name => options[name] = notification[name]);\n                yield this.broadcast({\n                    type: 'NOTIFICATION_CLICK',\n                    data: { action, notification: options },\n                });\n            });\n        }\n        reportStatus(client, promise, nonce) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const response = { type: 'STATUS', nonce, status: true };\n                try {\n                    yield promise;\n                    client.postMessage(response);\n                }\n                catch (e) {\n                    client.postMessage(Object.assign(Object.assign({}, response), { status: false, error: e.toString() }));\n                }\n            });\n        }\n        updateClient(client) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Figure out which version the client is on. If it's not on the latest,\n                // it needs to be moved.\n                const existing = this.clientVersionMap.get(client.id);\n                if (existing === this.latestHash) {\n                    // Nothing to do, this client is already on the latest version.\n                    return;\n                }\n                // Switch the client over.\n                let previous = undefined;\n                // Look up the application data associated with the existing version. If there\n                // isn't any, fall back on using the hash.\n                if (existing !== undefined) {\n                    const existingVersion = this.versions.get(existing);\n                    previous = this.mergeHashWithAppData(existingVersion.manifest, existing);\n                }\n                // Set the current version used by the client, and sync the mapping to disk.\n                this.clientVersionMap.set(client.id, this.latestHash);\n                yield this.sync();\n                // Notify the client about this activation.\n                const current = this.versions.get(this.latestHash);\n                const notice = {\n                    type: 'UPDATE_ACTIVATED',\n                    previous,\n                    current: this.mergeHashWithAppData(current.manifest, this.latestHash),\n                };\n                client.postMessage(notice);\n            });\n        }\n        handleFetch(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                try {\n                    // Ensure the SW instance has been initialized.\n                    yield this.ensureInitialized(event);\n                }\n                catch (_a) {\n                    // Since the SW is already committed to responding to the currently active request,\n                    // respond with a network fetch.\n                    return this.safeFetch(event.request);\n                }\n                // On navigation requests, check for new updates.\n                if (event.request.mode === 'navigate' && !this.scheduledNavUpdateCheck) {\n                    this.scheduledNavUpdateCheck = true;\n                    this.idle.schedule('check-updates-on-navigation', () => __awaiter$5(this, void 0, void 0, function* () {\n                        this.scheduledNavUpdateCheck = false;\n                        yield this.checkForUpdate();\n                    }));\n                }\n                // Decide which version of the app to use to serve this request. This is asynchronous as in\n                // some cases, a record will need to be written to disk about the assignment that is made.\n                const appVersion = yield this.assignVersion(event);\n                // Bail out\n                if (appVersion === null) {\n                    event.waitUntil(this.idle.trigger());\n                    return this.safeFetch(event.request);\n                }\n                let res = null;\n                try {\n                    // Handle the request. First try the AppVersion. If that doesn't work, fall back on the\n                    // network.\n                    res = yield appVersion.handleFetch(event.request, event);\n                }\n                catch (err) {\n                    if (err.isCritical) {\n                        // Something went wrong with the activation of this version.\n                        yield this.versionFailed(appVersion, err);\n                        event.waitUntil(this.idle.trigger());\n                        return this.safeFetch(event.request);\n                    }\n                    throw err;\n                }\n                // The AppVersion will only return null if the manifest doesn't specify what to do about this\n                // request. In that case, just fall back on the network.\n                if (res === null) {\n                    event.waitUntil(this.idle.trigger());\n                    return this.safeFetch(event.request);\n                }\n                // Trigger the idle scheduling system. The Promise returned by trigger() will resolve after\n                // a specific amount of time has passed. If trigger() hasn't been called again by then (e.g.\n                // on a subsequent request), the idle task queue will be drained and the Promise won't resolve\n                // until that operation is complete as well.\n                event.waitUntil(this.idle.trigger());\n                // The AppVersion returned a usable response, so return it.\n                return res;\n            });\n        }\n        /**\n         * Attempt to quickly reach a state where it's safe to serve responses.\n         */\n        initialize() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // On initialization, all of the serialized state is read out of the 'control'\n                // table. This includes:\n                // - map of hashes to manifests of currently loaded application versions\n                // - map of client IDs to their pinned versions\n                // - record of the most recently fetched manifest hash\n                //\n                // If these values don't exist in the DB, then this is the either the first time\n                // the SW has run or the DB state has been wiped or is inconsistent. In that case,\n                // load a fresh copy of the manifest and reset the state from scratch.\n                // Open up the DB table.\n                const table = yield this.db.open('control');\n                // Attempt to load the needed state from the DB. If this fails, the catch {} block\n                // will populate these variables with freshly constructed values.\n                let manifests, assignments, latest;\n                try {\n                    // Read them from the DB simultaneously.\n                    [manifests, assignments, latest] = yield Promise.all([\n                        table.read('manifests'),\n                        table.read('assignments'),\n                        table.read('latest'),\n                    ]);\n                    // Successfully loaded from saved state. This implies a manifest exists, so\n                    // the update check needs to happen in the background.\n                    this.idle.schedule('init post-load (update, cleanup)', () => __awaiter$5(this, void 0, void 0, function* () {\n                        yield this.checkForUpdate();\n                        try {\n                            yield this.cleanupCaches();\n                        }\n                        catch (err) {\n                            // Nothing to do - cleanup failed. Just log it.\n                            this.debugger.log(err, 'cleanupCaches @ init post-load');\n                        }\n                    }));\n                }\n                catch (_) {\n                    // Something went wrong. Try to start over by fetching a new manifest from the\n                    // server and building up an empty initial state.\n                    const manifest = yield this.fetchLatestManifest();\n                    const hash = hashManifest(manifest);\n                    manifests = {};\n                    manifests[hash] = manifest;\n                    assignments = {};\n                    latest = { latest: hash };\n                    // Save the initial state to the DB.\n                    yield Promise.all([\n                        table.write('manifests', manifests),\n                        table.write('assignments', assignments),\n                        table.write('latest', latest),\n                    ]);\n                }\n                // At this point, either the state has been loaded successfully, or fresh state\n                // with a new copy of the manifest has been produced. At this point, the `Driver`\n                // can have its internals hydrated from the state.\n                // Initialize the `versions` map by setting each hash to a new `AppVersion` instance\n                // for that manifest.\n                Object.keys(manifests).forEach((hash) => {\n                    const manifest = manifests[hash];\n                    // If the manifest is newly initialized, an AppVersion may have already been\n                    // created for it.\n                    if (!this.versions.has(hash)) {\n                        this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash));\n                    }\n                });\n                // Map each client ID to its associated hash. Along the way, verify that the hash\n                // is still valid for that client ID. It should not be possible for a client to\n                // still be associated with a hash that was since removed from the state.\n                Object.keys(assignments).forEach((clientId) => {\n                    const hash = assignments[clientId];\n                    if (this.versions.has(hash)) {\n                        this.clientVersionMap.set(clientId, hash);\n                    }\n                    else {\n                        this.clientVersionMap.set(clientId, latest.latest);\n                        this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`);\n                    }\n                });\n                // Set the latest version.\n                this.latestHash = latest.latest;\n                // Finally, assert that the latest version is in fact loaded.\n                if (!this.versions.has(latest.latest)) {\n                    throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`);\n                }\n                // Finally, wait for the scheduling of initialization of all versions in the\n                // manifest. Ordinarily this just schedules the initializations to happen during\n                // the next idle period, but in development mode this might actually wait for the\n                // full initialization.\n                // If any of these initializations fail, versionFailed() will be called either\n                // synchronously or asynchronously to handle the failure and re-map clients.\n                yield Promise.all(Object.keys(manifests).map((hash) => __awaiter$5(this, void 0, void 0, function* () {\n                    try {\n                        // Attempt to schedule or initialize this version. If this operation is\n                        // successful, then initialization either succeeded or was scheduled. If\n                        // it fails, then full initialization was attempted and failed.\n                        yield this.scheduleInitialization(this.versions.get(hash));\n                    }\n                    catch (err) {\n                        this.debugger.log(err, `initialize: schedule init of ${hash}`);\n                        return false;\n                    }\n                })));\n            });\n        }\n        lookupVersionByHash(hash, debugName = 'lookupVersionByHash') {\n            // The version should exist, but check just in case.\n            if (!this.versions.has(hash)) {\n                throw new Error(`Invariant violated (${debugName}): want AppVersion for ${hash} but not loaded`);\n            }\n            return this.versions.get(hash);\n        }\n        /**\n         * Decide which version of the manifest to use for the event.\n         */\n        assignVersion(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // First, check whether the event has a (non empty) client ID. If it does, the version may\n                // already be associated.\n                const clientId = event.clientId;\n                if (clientId) {\n                    // Check if there is an assigned client id.\n                    if (this.clientVersionMap.has(clientId)) {\n                        // There is an assignment for this client already.\n                        const hash = this.clientVersionMap.get(clientId);\n                        let appVersion = this.lookupVersionByHash(hash, 'assignVersion');\n                        // Ordinarily, this client would be served from its assigned version. But, if this\n                        // request is a navigation request, this client can be updated to the latest\n                        // version immediately.\n                        if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash &&\n                            appVersion.isNavigationRequest(event.request)) {\n                            // Update this client to the latest version immediately.\n                            if (this.latestHash === null) {\n                                throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                            }\n                            const client = yield this.scope.clients.get(clientId);\n                            yield this.updateClient(client);\n                            appVersion = this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                        }\n                        // TODO: make sure the version is valid.\n                        return appVersion;\n                    }\n                    else {\n                        // This is the first time this client ID has been seen. Whether the SW is in a\n                        // state to handle new clients depends on the current readiness state, so check\n                        // that first.\n                        if (this.state !== DriverReadyState.NORMAL) {\n                            // It's not safe to serve new clients in the current state. It's possible that\n                            // this is an existing client which has not been mapped yet (see below) but\n                            // even if that is the case, it's invalid to make an assignment to a known\n                            // invalid version, even if that assignment was previously implicit. Return\n                            // undefined here to let the caller know that no assignment is possible at\n                            // this time.\n                            return null;\n                        }\n                        // It's safe to handle this request. Two cases apply. Either:\n                        // 1) the browser assigned a client ID at the time of the navigation request, and\n                        //    this is truly the first time seeing this client, or\n                        // 2) a navigation request came previously from the same client, but with no client\n                        //    ID attached. Browsers do this to avoid creating a client under the origin in\n                        //    the event the navigation request is just redirected.\n                        //\n                        // In case 1, the latest version can safely be used.\n                        // In case 2, the latest version can be used, with the assumption that the previous\n                        // navigation request was answered under the same version. This assumption relies\n                        // on the fact that it's unlikely an update will come in between the navigation\n                        // request and requests for subsequent resources on that page.\n                        // First validate the current state.\n                        if (this.latestHash === null) {\n                            throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                        }\n                        // Pin this client ID to the current latest version, indefinitely.\n                        this.clientVersionMap.set(clientId, this.latestHash);\n                        yield this.sync();\n                        // Return the latest `AppVersion`.\n                        return this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                    }\n                }\n                else {\n                    // No client ID was associated with the request. This must be a navigation request\n                    // for a new client. First check that the SW is accepting new clients.\n                    if (this.state !== DriverReadyState.NORMAL) {\n                        return null;\n                    }\n                    // Serve it with the latest version, and assume that the client will actually get\n                    // associated with that version on the next request.\n                    // First validate the current state.\n                    if (this.latestHash === null) {\n                        throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                    }\n                    // Return the latest `AppVersion`.\n                    return this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                }\n            });\n        }\n        fetchLatestManifest(ignoreOfflineError = false) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const res = yield this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));\n                if (!res.ok) {\n                    if (res.status === 404) {\n                        yield this.deleteAllCaches();\n                        yield this.scope.registration.unregister();\n                    }\n                    else if (res.status === 504 && ignoreOfflineError) {\n                        return null;\n                    }\n                    throw new Error(`Manifest fetch failed! (status: ${res.status})`);\n                }\n                this.lastUpdateCheck = this.adapter.time;\n                return res.json();\n            });\n        }\n        deleteAllCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield (yield this.scope.caches.keys())\n                    .filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:`))\n                    .reduce((previous, key) => __awaiter$5(this, void 0, void 0, function* () {\n                    yield Promise.all([\n                        previous,\n                        this.scope.caches.delete(key),\n                    ]);\n                }), Promise.resolve());\n            });\n        }\n        /**\n         * Schedule the SW's attempt to reach a fully prefetched state for the given AppVersion\n         * when the SW is not busy and has connectivity. This returns a Promise which must be\n         * awaited, as under some conditions the AppVersion might be initialized immediately.\n         */\n        scheduleInitialization(appVersion) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const initialize = () => __awaiter$5(this, void 0, void 0, function* () {\n                    try {\n                        yield appVersion.initializeFully();\n                    }\n                    catch (err) {\n                        this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`);\n                        yield this.versionFailed(appVersion, err);\n                    }\n                });\n                // TODO: better logic for detecting localhost.\n                if (this.scope.registration.scope.indexOf('://localhost') > -1) {\n                    return initialize();\n                }\n                this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize);\n            });\n        }\n        versionFailed(appVersion, err) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // This particular AppVersion is broken. First, find the manifest hash.\n                const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);\n                if (broken === undefined) {\n                    // This version is no longer in use anyway, so nobody cares.\n                    return;\n                }\n                const brokenHash = broken[0];\n                const affectedClients = Array.from(this.clientVersionMap.entries())\n                    .filter(([clientId, hash]) => hash === brokenHash)\n                    .map(([clientId]) => clientId);\n                // TODO: notify affected apps.\n                // The action taken depends on whether the broken manifest is the active (latest) or not.\n                // If so, the SW cannot accept new clients, but can continue to service old ones.\n                if (this.latestHash === brokenHash) {\n                    // The latest manifest is broken. This means that new clients are at the mercy of the\n                    // network, but caches continue to be valid for previous versions. This is\n                    // unfortunate but unavoidable.\n                    this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;\n                    this.stateMessage = `Degraded due to: ${errorToString(err)}`;\n                    // Cancel the binding for the affected clients.\n                    affectedClients.forEach(clientId => this.clientVersionMap.delete(clientId));\n                }\n                else {\n                    // The latest version is viable, but this older version isn't. The only\n                    // possible remedy is to stop serving the older version and go to the network.\n                    // Put the affected clients on the latest version.\n                    affectedClients.forEach(clientId => this.clientVersionMap.set(clientId, this.latestHash));\n                }\n                try {\n                    yield this.sync();\n                }\n                catch (err2) {\n                    // We are already in a bad state. No need to make things worse.\n                    // Just log the error and move on.\n                    this.debugger.log(err2, `Driver.versionFailed(${err.message || err})`);\n                }\n            });\n        }\n        setupUpdate(manifest, hash) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash);\n                // Firstly, check if the manifest version is correct.\n                if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {\n                    yield this.deleteAllCaches();\n                    yield this.scope.registration.unregister();\n                    throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`);\n                }\n                // Cause the new version to become fully initialized. If this fails, then the\n                // version will not be available for use.\n                yield newVersion.initializeFully(this);\n                // Install this as an active version of the app.\n                this.versions.set(hash, newVersion);\n                // Future new clients will use this hash as the latest version.\n                this.latestHash = hash;\n                // If we are in `EXISTING_CLIENTS_ONLY` mode (meaning we didn't have a clean copy of the last\n                // latest version), we can now recover to `NORMAL` mode and start accepting new clients.\n                if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) {\n                    this.state = DriverReadyState.NORMAL;\n                    this.stateMessage = '(nominal)';\n                }\n                yield this.sync();\n                yield this.notifyClientsAboutUpdate(newVersion);\n            });\n        }\n        checkForUpdate() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                let hash = '(unknown)';\n                try {\n                    const manifest = yield this.fetchLatestManifest(true);\n                    if (manifest === null) {\n                        // Client or server offline. Unable to check for updates at this time.\n                        // Continue to service clients (existing and new).\n                        this.debugger.log('Check for update aborted. (Client or server offline.)');\n                        return false;\n                    }\n                    hash = hashManifest(manifest);\n                    // Check whether this is really an update.\n                    if (this.versions.has(hash)) {\n                        return false;\n                    }\n                    yield this.setupUpdate(manifest, hash);\n                    return true;\n                }\n                catch (err) {\n                    this.debugger.log(err, `Error occurred while updating to manifest ${hash}`);\n                    this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;\n                    this.stateMessage = `Degraded due to failed initialization: ${errorToString(err)}`;\n                    return false;\n                }\n            });\n        }\n        /**\n         * Synchronize the existing state to the underlying database.\n         */\n        sync() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Open up the DB table.\n                const table = yield this.db.open('control');\n                // Construct a serializable map of hashes to manifests.\n                const manifests = {};\n                this.versions.forEach((version, hash) => { manifests[hash] = version.manifest; });\n                // Construct a serializable map of client ids to version hashes.\n                const assignments = {};\n                this.clientVersionMap.forEach((hash, clientId) => { assignments[clientId] = hash; });\n                // Record the latest entry. Since this is a sync which is necessarily happening after\n                // initialization, latestHash should always be valid.\n                const latest = {\n                    latest: this.latestHash,\n                };\n                // Synchronize all of these.\n                yield Promise.all([\n                    table.write('manifests', manifests),\n                    table.write('assignments', assignments),\n                    table.write('latest', latest),\n                ]);\n            });\n        }\n        cleanupCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Query for all currently active clients, and list the client ids. This may skip\n                // some clients in the browser back-forward cache, but not much can be done about\n                // that.\n                const activeClients = (yield this.scope.clients.matchAll()).map(client => client.id);\n                // A simple list of client ids that the SW has kept track of. Subtracting\n                // activeClients from this list will result in the set of client ids which are\n                // being tracked but are no longer used in the browser, and thus can be cleaned up.\n                const knownClients = Array.from(this.clientVersionMap.keys());\n                // Remove clients in the clientVersionMap that are no longer active.\n                knownClients.filter(id => activeClients.indexOf(id) === -1)\n                    .forEach(id => this.clientVersionMap.delete(id));\n                // Next, determine the set of versions which are still used. All others can be\n                // removed.\n                const usedVersions = new Set();\n                this.clientVersionMap.forEach((version, _) => usedVersions.add(version));\n                // Collect all obsolete versions by filtering out used versions from the set of all versions.\n                const obsoleteVersions = Array.from(this.versions.keys())\n                    .filter(version => !usedVersions.has(version) && version !== this.latestHash);\n                // Remove all the versions which are no longer used.\n                yield obsoleteVersions.reduce((previous, version) => __awaiter$5(this, void 0, void 0, function* () {\n                    // Wait for the other cleanup operations to complete.\n                    yield previous;\n                    // Try to get past the failure of one particular version to clean up (this\n                    // shouldn't happen, but handle it just in case).\n                    try {\n                        // Get ahold of the AppVersion for this particular hash.\n                        const instance = this.versions.get(version);\n                        // Delete it from the canonical map.\n                        this.versions.delete(version);\n                        // Clean it up.\n                        yield instance.cleanup();\n                    }\n                    catch (err) {\n                        // Oh well? Not much that can be done here. These caches will be removed when\n                        // the SW revs its format version, which happens from time to time.\n                        this.debugger.log(err, `cleanupCaches - cleanup ${version}`);\n                    }\n                }), Promise.resolve());\n                // Commit all the changes to the saved state.\n                yield this.sync();\n            });\n        }\n        /**\n         * Delete caches that were used by older versions of `@angular/service-worker` to avoid running\n         * into storage quota limitations imposed by browsers.\n         * (Since at this point the SW has claimed all clients, it is safe to remove those caches.)\n         */\n        cleanupOldSwCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const cacheNames = yield this.scope.caches.keys();\n                const oldSwCacheNames = cacheNames.filter(name => /^ngsw:(?!\\/)/.test(name));\n                yield Promise.all(oldSwCacheNames.map(name => this.scope.caches.delete(name)));\n            });\n        }\n        /**\n         * Determine if a specific version of the given resource is cached anywhere within the SW,\n         * and fetch it if so.\n         */\n        lookupResourceWithHash(url, hash) {\n            return Array\n                // Scan through the set of all cached versions, valid or otherwise. It's safe to do such\n                // lookups even for invalid versions as the cached version of a resource will have the\n                // same hash regardless.\n                .from(this.versions.values())\n                // Reduce the set of versions to a single potential result. At any point along the\n                // reduction, if a response has already been identified, then pass it through, as no\n                // future operation could change the response. If no response has been found yet, keep\n                // checking versions until one is or until all versions have been exhausted.\n                .reduce((prev, version) => __awaiter$5(this, void 0, void 0, function* () {\n                // First, check the previous result. If a non-null result has been found already, just\n                // return it.\n                if ((yield prev) !== null) {\n                    return prev;\n                }\n                // No result has been found yet. Try the next `AppVersion`.\n                return version.lookupResourceWithHash(url, hash);\n            }), Promise.resolve(null));\n        }\n        lookupResourceWithoutHash(url) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const version = this.versions.get(this.latestHash);\n                return version ? version.lookupResourceWithoutHash(url) : null;\n            });\n        }\n        previouslyCachedResources() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const version = this.versions.get(this.latestHash);\n                return version ? version.previouslyCachedResources() : [];\n            });\n        }\n        recentCacheStatus(url) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const version = this.versions.get(this.latestHash);\n                return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED;\n            });\n        }\n        mergeHashWithAppData(manifest, hash) {\n            return {\n                hash,\n                appData: manifest.appData,\n            };\n        }\n        notifyClientsAboutUpdate(next) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const clients = yield this.scope.clients.matchAll();\n                yield clients.reduce((previous, client) => __awaiter$5(this, void 0, void 0, function* () {\n                    yield previous;\n                    // Firstly, determine which version this client is on.\n                    const version = this.clientVersionMap.get(client.id);\n                    if (version === undefined) {\n                        // Unmapped client - assume it's the latest.\n                        return;\n                    }\n                    if (version === this.latestHash) {\n                        // Client is already on the latest version, no need for a notification.\n                        return;\n                    }\n                    const current = this.versions.get(version);\n                    // Send a notice.\n                    const notice = {\n                        type: 'UPDATE_AVAILABLE',\n                        current: this.mergeHashWithAppData(current.manifest, version),\n                        available: this.mergeHashWithAppData(next.manifest, this.latestHash),\n                    };\n                    client.postMessage(notice);\n                }), Promise.resolve());\n            });\n        }\n        broadcast(msg) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const clients = yield this.scope.clients.matchAll();\n                clients.forEach(client => { client.postMessage(msg); });\n            });\n        }\n        debugState() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                return {\n                    state: DriverReadyState[this.state],\n                    why: this.stateMessage,\n                    latestHash: this.latestHash,\n                    lastUpdateCheck: this.lastUpdateCheck,\n                };\n            });\n        }\n        debugVersions() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Build list of versions.\n                return Array.from(this.versions.keys()).map(hash => {\n                    const version = this.versions.get(hash);\n                    const clients = Array.from(this.clientVersionMap.entries())\n                        .filter(([clientId, version]) => version === hash)\n                        .map(([clientId, version]) => clientId);\n                    return {\n                        hash,\n                        manifest: version.manifest, clients,\n                        status: '',\n                    };\n                });\n            });\n        }\n        debugIdleState() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                return {\n                    queue: this.idle.taskDescriptions,\n                    lastTrigger: this.idle.lastTrigger,\n                    lastRun: this.idle.lastRun,\n                };\n            });\n        }\n        safeFetch(req) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                try {\n                    return yield this.scope.fetch(req);\n                }\n                catch (err) {\n                    this.debugger.log(err, `Driver.fetch(${req.url})`);\n                    return this.adapter.newResponse(null, {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    const scope = self;\n    const adapter = new Adapter(scope);\n    const driver = new Driver(scope, adapter, new CacheDatabase(scope, adapter));\n\n}());\n"
  },
  {
    "path": "Chapter_11/WorldCities/wwwroot/ngsw.json",
    "content": "{\n  \"configVersion\": 1,\n  \"timestamp\": 1577992220985,\n  \"index\": \"/index.html\",\n  \"assetGroups\": [\n    {\n      \"name\": \"app\",\n      \"installMode\": \"prefetch\",\n      \"updateMode\": \"prefetch\",\n      \"urls\": [\n        \"/index.html\",\n        \"/main-es2015.0dfa6088d7ebe21b943a.js\",\n        \"/main-es5.0dfa6088d7ebe21b943a.js\",\n        \"/manifest.webmanifest\",\n        \"/polyfills-es2015.58725a5910daef768ca8.js\",\n        \"/polyfills-es5.079443d8bcab7d711023.js\",\n        \"/runtime-es2015.e59a6cd8f1b6ab0c3f29.js\",\n        \"/runtime-es5.e59a6cd8f1b6ab0c3f29.js\",\n        \"/styles.519f5f6cb11c0ac03ff3.css\"\n      ],\n      \"patterns\": []\n    },\n    {\n      \"name\": \"assets\",\n      \"installMode\": \"lazy\",\n      \"updateMode\": \"prefetch\",\n      \"urls\": [\n        \"/assets/icons/icon-128x128.png\",\n        \"/assets/icons/icon-144x144.png\",\n        \"/assets/icons/icon-152x152.png\",\n        \"/assets/icons/icon-192x192.png\",\n        \"/assets/icons/icon-384x384.png\",\n        \"/assets/icons/icon-512x512.png\",\n        \"/assets/icons/icon-72x72.png\",\n        \"/assets/icons/icon-96x96.png\"\n      ],\n      \"patterns\": []\n    }\n  ],\n  \"dataGroups\": [],\n  \"hashTable\": {\n    \"/assets/icons/icon-128x128.png\": \"664808390a86b765b38840d2229bb8e77f23315f\",\n    \"/assets/icons/icon-144x144.png\": \"7d2df9f8dfadc3ba692838dc5a05fdda171ffe46\",\n    \"/assets/icons/icon-152x152.png\": \"328dddc298922fee24ca15f6188a8be20b1b5e85\",\n    \"/assets/icons/icon-192x192.png\": \"36caee50fcc2ddeca25acd0a74ccb12a04281dd2\",\n    \"/assets/icons/icon-384x384.png\": \"f3fe7601922932a95550a3fa767efec8eb2c54f2\",\n    \"/assets/icons/icon-512x512.png\": \"8e215e2e14a8fe8ed23ad313003a2da652b0acd9\",\n    \"/assets/icons/icon-72x72.png\": \"cbf6ac39ff640851fd721545fa68595aaf715e50\",\n    \"/assets/icons/icon-96x96.png\": \"745d24bafdf890ad4f63e9c4e69c713212849802\",\n    \"/index.html\": \"b73c833cf32b543e091e8683ded1744c3d7e0c76\",\n    \"/main-es2015.0dfa6088d7ebe21b943a.js\": \"2676c9e57672d9cec97ee29945ab8a60cf7dc916\",\n    \"/main-es5.0dfa6088d7ebe21b943a.js\": \"9332386d541e52f5269f53ca4be1aa8aa4576841\",\n    \"/manifest.webmanifest\": \"43ccf4b7574d245892754a78dcf31cfa4e2ef956\",\n    \"/polyfills-es2015.58725a5910daef768ca8.js\": \"3e29b382a75f751f979baff5606aeb37d3f66c3a\",\n    \"/polyfills-es5.079443d8bcab7d711023.js\": \"f2bfdffc3174ace137ac2516e8682571b6f460b0\",\n    \"/runtime-es2015.e59a6cd8f1b6ab0c3f29.js\": \"a9aafcf49f49145093fc831efd9b8e2f6c71bb9c\",\n    \"/runtime-es5.e59a6cd8f1b6ab0c3f29.js\": \"a9aafcf49f49145093fc831efd9b8e2f6c71bb9c\",\n    \"/styles.519f5f6cb11c0ac03ff3.css\": \"722333c0e596cfbb27969f7ac040dcef942da1ca\"\n  },\n  \"navigationUrls\": [\n    {\n      \"positive\": true,\n      \"regex\": \"^\\\\/.*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*\\\\.[^/]*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*__[^/]*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*__[^/]*\\\\/.*$\"\n    }\n  ]\n}"
  },
  {
    "path": "Chapter_11/WorldCities/wwwroot/safety-worker.js",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\n\n// tslint:disable:no-console\n\nself.addEventListener('install', event => { self.skipWaiting(); });\n\nself.addEventListener('activate', event => {\n  event.waitUntil(self.clients.claim());\n  self.registration.unregister().then(\n      () => { console.log('NGSW Safety Worker - unregistered old service worker'); });\n});\n"
  },
  {
    "path": "Chapter_11.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"HealthCheck\", \"Chapter_11\\HealthCheck\\HealthCheck.csproj\", \"{998294BE-7042-413D-A864-0CF65742E2CC}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_11\\WorldCities\\WorldCities.csproj\", \"{926BF7D9-BE35-4547-83F9-4DD58A7D757B}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{998294BE-7042-413D-A864-0CF65742E2CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{998294BE-7042-413D-A864-0CF65742E2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{998294BE-7042-413D-A864-0CF65742E2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{998294BE-7042-413D-A864-0CF65742E2CC}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{926BF7D9-BE35-4547-83F9-4DD58A7D757B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{926BF7D9-BE35-4547-83F9-4DD58A7D757B}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{926BF7D9-BE35-4547-83F9-4DD58A7D757B}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{926BF7D9-BE35-4547-83F9-4DD58A7D757B}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Chapter_12/HealthCheck/.config/dotnet-tools.json",
    "content": "{\n  \"version\": 1,\n  \"isRoot\": true,\n  \"tools\": {\n    \"dotnet-ef\": {\n      \"version\": \"3.1.0\",\n      \"commands\": [\n        \"dotnet-ef\"\n      ]\n    }\n  }\n}"
  },
  {
    "path": "Chapter_12/HealthCheck/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/README.md",
    "content": "# HealthCheck\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"HealthCheck\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\n              \"src/assets\",\n              \"src/manifest.webmanifest\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true,\n              \"serviceWorker\": true,\n              \"ngswConfigPath\": \"ngsw-config.json\"\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"HealthCheck:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"HealthCheck:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"HealthCheck:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [ \"src/styles.css\" ],\n            \"scripts\": [],\n            \"assets\": [\n              \"src/assets\",\n              \"src/manifest.webmanifest\"\n            ]\n\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\"src/tsconfig.app.json\", \"src/tsconfig.spec.json\"],\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"HealthCheck-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"HealthCheck:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\"**/node_modules/**\"]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"HealthCheck\"\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/ngsw-config.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/service-worker/config/schema.json\",\n  \"index\": \"/index.html\",\n  \"assetGroups\": [\n    {\n      \"name\": \"app\",\n      \"installMode\": \"prefetch\",\n      \"resources\": {\n        \"files\": [\n          \"/favicon.ico\",\n          \"/index.html\",\n          \"/manifest.webmanifest\",\n          \"/*.css\",\n          \"/*.js\"\n        ]\n      }\n    },\n    {\n      \"name\": \"assets\",\n      \"installMode\": \"lazy\",\n      \"updateMode\": \"prefetch\",\n      \"resources\": {\n        \"files\": [\n          \"/assets/**\",           \n          \"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)\"\n        ]\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/package.json",
    "content": "{\n  \"name\": \"healthcheck\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run HealthCheck:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/router\": \"9.0.0\",\n    \"@angular/service-worker\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/app.component.html",
    "content": "<body>\n\n  <div class=\"alert alert-warning\" *ngIf=\"!isConnected\">\n    <strong>WARNING</strong>: the app is currently <i>offline</i>:\n    some features that rely upon the back-end might not work as expected.\n    This message will automatically disappear\n    as soon as the internet connection becomes available again.\n  </div>\n\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { ConnectionService } from '../ng-connection-service/connection-service.service';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n\n  hasNetworkConnection: boolean;\n  hasInternetAccess: boolean;\n  isConnected: boolean;\n  status: string;\n\n  constructor(private connectionService: ConnectionService) {\n    this.connectionService.updateOptions({\n      heartbeatUrl: \"/isOnline.txt\"\n    });\n    this.connectionService.monitor().subscribe(currentState => {\n      this.hasNetworkConnection = currentState.hasNetworkConnection;\n      this.hasInternetAccess = currentState.hasInternetAccess;\n      if (this.hasNetworkConnection && this.hasInternetAccess) {\n        this.isConnected = true;\n        this.status = 'ONLINE';\n      }\n      else {\n        this.isConnected = false;\n        this.status = 'OFFLINE';\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\nimport { ServiceWorkerModule } from '@angular/service-worker';\nimport { environment } from '../environments/environment';\n\nimport { AppComponent } from './app.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { HealthCheckComponent } from './health-check/health-check.component';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavMenuComponent,\n    HomeComponent,\n    HealthCheckComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    RouterModule.forRoot([\n      { path: '', component: HomeComponent, pathMatch: 'full' },\n      { path: 'health-check', component: HealthCheckComponent }\n    ]),\n    ServiceWorkerModule.register(\n      'ngsw-worker.js',\n      {\n        registrationStrategy: 'registerImmediately'\n      })\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/health-check/health-check.component.css",
    "content": ".status {\n  font-weight: bold;\n}\n\n.Healthy {\n  color: green;\n}\n\n.Degraded {\n  color: orange;\n}\n\n.Unhealthy {\n  color: red;\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/health-check/health-check.component.html",
    "content": "<h1>Health Check</h1>\n\n<p>Here are the results of our health check:</p>\n\n<p *ngIf=\"!result\"><em>Loading...</em></p>\n\n<table class='table table-striped' aria-labelledby=\"tableLabel\" *ngIf=\"result\">\n  <thead>\n    <tr>\n      <th>Name</th>\n      <th>Response Time</th>\n      <th>Status</th>\n      <th>Description</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr *ngFor=\"let check of result.checks\">\n      <td>{{ check.name }}</td>\n      <td>{{ check.responseTime }}</td>\n      <td class=\"status {{ check.status }}\">{{ check.status }}</td>\n      <td>{{ check.description }}</td>\n    </tr>\n  </tbody>\n</table>\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/health-check/health-check.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\n\n@Component({\n    selector: 'app-health-check',\n    templateUrl: './health-check.component.html',\n    styleUrls: ['./health-check.component.css']\n})\nexport class HealthCheckComponent {\n  public result: Result;\n\n  constructor(\n    private http: HttpClient,\n    @Inject('BASE_URL') private baseUrl: string) {\n  }\n\n  ngOnInit() {\n    this.http.get<Result>(this.baseUrl + 'hc').subscribe(result => {\n      this.result = result;\n    }, error => console.error(error));\n  }\n}\n\ninterface Result {\n    checks: Check[];\n    totalStatus: string;\n    totalResponseTime: number;\n}\n\ninterface Check {\n    name: string;\n    status: string;\n    responseTime: number;\n}\n\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">HealthCheck</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/health-check']\"\n              >Health Check</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>HealthCheck</title>\n    <base href=\"/\" />\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n\n    <!-- PWA required files -->\n    <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&amp;display=swap\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n    <link rel=\"manifest\" crossorigin=\"use-credentials\" href=\"manifest.webmanifest\">\n    <meta name=\"theme-color\" content=\"#1976d2\">\n\n  </head>\n  <body>\n    <app-root>Loading...</app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/manifest.webmanifest",
    "content": "{\n  \"name\": \"HealthCheck\",\n  \"short_name\": \"HealthCheck\",\n  \"theme_color\": \"#2196f3\",\n  \"background_color\": \"#2196f3\",\n  \"display\": \"standalone\",\n  \"Scope\": \"/\",\n  \"start_url\": \"/\",\n  \"icons\": [\n    {\n      \"src\": \"assets/icons/icon-72x72.png\",\n      \"sizes\": \"72x72\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-96x96.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-128x128.png\",\n      \"sizes\": \"128x128\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-144x144.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-152x152.png\",\n      \"sizes\": \"152x152\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-384x384.png\",\n      \"sizes\": \"384x384\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"splash_pages\": null\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/ng-connection-service/connection-service.module.ts",
    "content": "import {NgModule} from '@angular/core';\nimport {ConnectionService} from './connection-service.service';\nimport {HttpClientModule} from '@angular/common/http';\n\n@NgModule({\n  imports: [HttpClientModule],\n  providers: [ConnectionService]\n})\nexport class ConnectionServiceModule {\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/ng-connection-service/connection-service.service.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { ConnectionService } from './connection-service.service';\n\ndescribe('ConnectionServiceService', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [ConnectionService]\n    });\n  });\n\n  it('should be created', inject([ConnectionService], (service: ConnectionService) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/ng-connection-service/connection-service.service.ts",
    "content": "import {EventEmitter, Inject, Injectable, InjectionToken, OnDestroy, Optional} from '@angular/core';\nimport {fromEvent, Observable, Subscription, timer} from 'rxjs';\nimport {debounceTime, delay, retryWhen, startWith, switchMap, tap} from 'rxjs/operators';\nimport {HttpClient} from '@angular/common/http';\nimport * as _ from 'lodash';\n\n/**\n * Instance of this interface is used to report current connection status.\n */\nexport interface ConnectionState {\n  /**\n   * \"True\" if browser has network connection. Determined by Window objects \"online\" / \"offline\" events.\n   */\n  hasNetworkConnection: boolean;\n  /**\n   * \"True\" if browser has Internet access. Determined by heartbeat system which periodically makes request to heartbeat Url.\n   */\n  hasInternetAccess: boolean;\n}\n\n/**\n * Instance of this interface could be used to configure \"ConnectionService\".\n */\nexport interface ConnectionServiceOptions {\n  /**\n   * Controls the Internet connectivity heartbeat system. Default value is 'true'.\n   */\n  enableHeartbeat?: boolean;\n  /**\n   * Url used for checking Internet connectivity, heartbeat system periodically makes \"HEAD\" requests to this URL to determine Internet\n   * connection status. Default value is \"//internethealthtest.org\".\n   */\n  heartbeatUrl?: string;\n  /**\n   * Interval used to check Internet connectivity specified in milliseconds. Default value is \"30000\".\n   */\n  heartbeatInterval?: number;\n  /**\n   * Interval used to retry Internet connectivity checks when an error is detected (when no Internet connection). Default value is \"1000\".\n   */\n  heartbeatRetryInterval?: number;\n  /**\n   * HTTP method used for requesting heartbeat Url. Default is 'head'.\n   */\n  requestMethod?: 'get' | 'post' | 'head' | 'options';\n\n}\n\n/**\n * InjectionToken for specifing ConnectionService options.\n */\nexport const ConnectionServiceOptionsToken: InjectionToken<ConnectionServiceOptions> = new InjectionToken('ConnectionServiceOptionsToken');\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class ConnectionService implements OnDestroy {\n  private static DEFAULT_OPTIONS: ConnectionServiceOptions = {\n    enableHeartbeat: true,\n    heartbeatUrl: '//internethealthtest.org',\n    heartbeatInterval: 30000,\n    heartbeatRetryInterval: 1000,\n    requestMethod: 'head'\n  };\n\n  private stateChangeEventEmitter = new EventEmitter<ConnectionState>();\n\n  private currentState: ConnectionState = {\n    hasInternetAccess: false,\n    hasNetworkConnection: window.navigator.onLine\n  };\n  private offlineSubscription: Subscription;\n  private onlineSubscription: Subscription;\n  private httpSubscription: Subscription;\n  private serviceOptions: ConnectionServiceOptions;\n\n  /**\n   * Current ConnectionService options. Notice that changing values of the returned object has not effect on service execution.\n   * You should use \"updateOptions\" function.\n   */\n  get options(): ConnectionServiceOptions {\n    return _.clone(this.serviceOptions);\n  }\n\n  constructor(private http: HttpClient, @Inject(ConnectionServiceOptionsToken) @Optional() options: ConnectionServiceOptions) {\n    this.serviceOptions = _.defaults({}, options, ConnectionService.DEFAULT_OPTIONS);\n\n    this.checkNetworkState();\n    this.checkInternetState();\n  }\n\n  private checkInternetState() {\n\n    if (!_.isNil(this.httpSubscription)) {\n      this.httpSubscription.unsubscribe();\n    }\n\n    if (this.serviceOptions.enableHeartbeat) {\n      this.httpSubscription = timer(0, this.serviceOptions.heartbeatInterval)\n        .pipe(\n          switchMap(() => this.http[this.serviceOptions.requestMethod](this.serviceOptions.heartbeatUrl, {responseType: 'text'})),\n          retryWhen(errors =>\n            errors.pipe(\n              // log error message\n              tap(val => {\n                console.error('Http error:', val);\n                this.currentState.hasInternetAccess = false;\n                this.emitEvent();\n              }),\n              // restart after 5 seconds\n              delay(this.serviceOptions.heartbeatRetryInterval)\n            )\n          )\n        )\n        .subscribe(result => {\n          this.currentState.hasInternetAccess = true;\n          this.emitEvent();\n        });\n    } else {\n      this.currentState.hasInternetAccess = false;\n      this.emitEvent();\n    }\n  }\n\n  private checkNetworkState() {\n    this.onlineSubscription = fromEvent(window, 'online').subscribe(() => {\n      this.currentState.hasNetworkConnection = true;\n      this.checkInternetState();\n      this.emitEvent();\n    });\n\n    this.offlineSubscription = fromEvent(window, 'offline').subscribe(() => {\n      this.currentState.hasNetworkConnection = false;\n      this.checkInternetState();\n      this.emitEvent();\n    });\n  }\n\n  private emitEvent() {\n    this.stateChangeEventEmitter.emit(this.currentState);\n  }\n\n  ngOnDestroy(): void {\n    try {\n      this.offlineSubscription.unsubscribe();\n      this.onlineSubscription.unsubscribe();\n      this.httpSubscription.unsubscribe();\n    } catch (e) {\n    }\n  }\n\n  /**\n   * Monitor Network & Internet connection status by subscribing to this observer. If you set \"reportCurrentState\" to \"false\" then\n   * function will not report current status of the connections when initially subscribed.\n   * @param reportCurrentState Report current state when initial subscription. Default is \"true\"\n   */\n  monitor(reportCurrentState = true): Observable<ConnectionState> {\n    return reportCurrentState ?\n      this.stateChangeEventEmitter.pipe(\n        debounceTime(300),\n        startWith(this.currentState),\n      )\n      :\n      this.stateChangeEventEmitter.pipe(\n        debounceTime(300)\n      );\n  }\n\n  /**\n   * Update options of the service. You could specify partial options object. Values that are not specified will use default / previous\n   * option values.\n   * @param options Partial option values.\n   */\n  updateOptions(options: Partial<ConnectionServiceOptions>) {\n    this.serviceOptions = _.defaults({}, options, this.serviceOptions);\n    this.checkInternetState();\n  }\n\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  },\n  \"angularCompilerOptions\": {\n    \"strictMetadataEmit\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_12/HealthCheck/CustomHealthCheckOptions.cs",
    "content": "﻿using Microsoft.AspNetCore.Diagnostics.HealthChecks;\nusing Microsoft.AspNetCore.Http;\nusing System.Linq;\nusing System.Net.Mime;\nusing System.Text.Json;\n\nnamespace HealthCheck\n{\n    public class CustomHealthCheckOptions : HealthCheckOptions\n    {\n        public CustomHealthCheckOptions() : base() \n        {\n            var jsonSerializerOptions = new JsonSerializerOptions() \n            { \n                WriteIndented = true \n            };\n\n            ResponseWriter = async (c, r) =>\n            {\n                c.Response.ContentType = MediaTypeNames.Application.Json;\n                c.Response.StatusCode = StatusCodes.Status200OK;\n\n                var result = JsonSerializer.Serialize(new\n                   {\n                      checks = r.Entries.Select(e => new\n                          {\n                              name = e.Key,\n                              responseTime = e.Value.Duration.TotalMilliseconds,\n                              status = e.Value.Status.ToString(),\n                              description = e.Value.Description\n                          }),\n                      totalStatus = r.Status,\n                      totalResponseTime = r.TotalDuration.TotalMilliseconds,\n                   }, jsonSerializerOptions);\n                await c.Response.WriteAsync(result);\n            };\n        }\n    }\n}"
  },
  {
    "path": "Chapter_12/HealthCheck/HealthCheck.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n    <RootNamespace>HealthCheck</RootNamespace>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Folder Include=\"Controllers\\\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Content Update=\"wwwroot\\favicon.ico\">\n      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n    <Content Update=\"wwwroot\\isOnline.txt\">\n      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n    </Content>\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_12/HealthCheck/ICMPHealthCheck.cs",
    "content": "﻿using Microsoft.Extensions.Diagnostics.HealthChecks;\nusing System;\nusing System.Net.NetworkInformation;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace HealthCheck\n{\n    public class ICMPHealthCheck : IHealthCheck\n    {\n        private string Host { get; set; }\n        private int Timeout { get; set; }\n\n        public ICMPHealthCheck(string host, int timeout)\n        {\n            Host = host;\n            Timeout = timeout;\n        }\n\n        public async Task<HealthCheckResult> CheckHealthAsync(\n            HealthCheckContext context,\n            CancellationToken cancellationToken = default)\n        {\n            try\n            {\n                using (var ping = new Ping())\n                {\n                    var reply = await ping.SendPingAsync(Host);\n\n                    switch (reply.Status)\n                    {\n                        case IPStatus.Success:\n                            var msg = String.Format(\n                                \"IMCP to {0} took {1} ms.\",\n                                Host,\n                                reply.RoundtripTime);\n\n                            return (reply.RoundtripTime > Timeout)\n                                ? HealthCheckResult.Degraded(msg)\n                                : HealthCheckResult.Healthy(msg);\n\n                        default:\n                            var err = String.Format(\n                                \"IMCP to {0} failed: {1}\",\n                                Host,\n                                reply.Status);\n                            return HealthCheckResult.Unhealthy(err);\n                    }\n                }\n            }\n            catch (Exception e)\n            {\n                var err = String.Format(\n                    \"IMCP to {0} failed: {1}\",\n                    Host,\n                    e.Message);\n                return HealthCheckResult.Unhealthy(err);\n            }\n        }\n    }\n}"
  },
  {
    "path": "Chapter_12/HealthCheck/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_12/HealthCheck/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace HealthCheck.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/Pages/_ViewImports.cshtml",
    "content": "@using HealthCheck\n@namespace HealthCheck.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_12/HealthCheck/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace HealthCheck\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/Startup.cs",
    "content": "using Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.AspNetCore.StaticFiles;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\n\nnamespace HealthCheck\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews();\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            services.AddHealthChecks()\n                .AddCheck(\"ICMP_01\", new ICMPHealthCheck(\"www.ryadel.com\", 100))\n                .AddCheck(\"ICMP_02\", new ICMPHealthCheck(\"www.google.com\", 100))\n                .AddCheck(\"ICMP_03\", new ICMPHealthCheck(\"www.does-not-exist.com\", 100));\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n\n            app.UseHttpsRedirection();\n\n            // add .webmanifest MIME-type support\n            FileExtensionContentTypeProvider provider = new FileExtensionContentTypeProvider();\n            provider.Mappings[\".webmanifest\"] = \"application/manifest+json\";\n\n            app.UseStaticFiles(new StaticFileOptions()\n            {\n                ContentTypeProvider = provider,\n                OnPrepareResponse = (context) =>\n                {\n                    if (context.File.Name == \"isOnline.txt\")\n                    {\n                        // disable caching for these files\n                        context.Context.Response.Headers.Add(\"Cache-Control\", \"no-cache, no-store\");\n                        context.Context.Response.Headers.Add(\"Expires\", \"-1\");\n                    }\n                    else\n                    {\n                        // Retrieve cache configuration from appsettings.json\n                        context.Context.Response.Headers[\"Cache-Control\"] =\n                            Configuration[\"StaticFiles:Headers:Cache-Control\"];\n                        context.Context.Response.Headers[\"Pragma\"] =\n                            Configuration[\"StaticFiles:Headers:Pragma\"];\n                        context.Context.Response.Headers[\"Expires\"] =\n                            Configuration[\"StaticFiles:Headers:Expires\"];\n                    }\n                }\n            });\n\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles(new StaticFileOptions()\n                {\n                    ContentTypeProvider = provider\n                });\n            }\n\n            app.UseRouting();\n\n            app.UseHealthChecks(\"/hc\", new CustomHealthCheckOptions());\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/appsettings.Development.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  },\n  \"StaticFiles\": {\n    \"Headers\": {\n      \"Cache-Control\": \"no-cache, no-store\",\n      \"Pragma\": \"no-cache\",\n      \"Expires\": \"-1\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/appsettings.json",
    "content": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\",\n  \"StaticFiles\": {\n    \"Headers\": {\n      \"Cache-Control\": \"max-age=3600\",\n      \"Pragma\": \"cache\",\n      \"Expires\": null\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/libman.json",
    "content": "{\n  \"version\": \"1.0\",\n  \"defaultProvider\": \"cdnjs\",\n  \"libraries\": []\n}"
  },
  {
    "path": "Chapter_12/HealthCheck/wwwroot/isOnline.txt",
    "content": "﻿."
  },
  {
    "path": "Chapter_12/HealthCheck/wwwroot/manifest.webmanifest",
    "content": "{\n  \"name\": \"HealthCheck\",\n  \"short_name\": \"HealthCheck\",\n  \"theme_color\": \"#2196f3\",\n  \"background_color\": \"#2196f3\",\n  \"display\": \"standalone\",\n  \"Scope\": \"/\",\n  \"start_url\": \"/\",\n  \"icons\": [\n    {\n      \"src\": \"assets/icons/icon-72x72.png\",\n      \"sizes\": \"72x72\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-96x96.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-128x128.png\",\n      \"sizes\": \"128x128\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-144x144.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-152x152.png\",\n      \"sizes\": \"152x152\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-384x384.png\",\n      \"sizes\": \"384x384\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"splash_pages\": null\n}\n"
  },
  {
    "path": "Chapter_12/HealthCheck/wwwroot/ngsw-worker.js",
    "content": "(function () {\n    'use strict';\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * Adapts the service worker to its runtime environment.\n     *\n     * Mostly, this is used to mock out identifiers which are otherwise read\n     * from the global scope.\n     */\n    class Adapter {\n        constructor(scope) {\n            // Suffixing `ngsw` with the baseHref to avoid clash of cache names\n            // for SWs with different scopes on the same domain.\n            const baseHref = this.parseUrl(scope.registration.scope).path;\n            this.cacheNamePrefix = 'ngsw:' + baseHref;\n        }\n        /**\n         * Wrapper around the `Request` constructor.\n         */\n        newRequest(input, init) {\n            return new Request(input, init);\n        }\n        /**\n         * Wrapper around the `Response` constructor.\n         */\n        newResponse(body, init) { return new Response(body, init); }\n        /**\n         * Wrapper around the `Headers` constructor.\n         */\n        newHeaders(headers) { return new Headers(headers); }\n        /**\n         * Test if a given object is an instance of `Client`.\n         */\n        isClient(source) { return (source instanceof Client); }\n        /**\n         * Read the current UNIX time in milliseconds.\n         */\n        get time() { return Date.now(); }\n        /**\n         * Extract the pathname of a URL.\n         */\n        parseUrl(url, relativeTo) {\n            // Workaround a Safari bug, see\n            // https://github.com/angular/angular/issues/31061#issuecomment-503637978\n            const parsed = !relativeTo ? new URL(url) : new URL(url, relativeTo);\n            return { origin: parsed.origin, path: parsed.pathname, search: parsed.search };\n        }\n        /**\n         * Wait for a given amount of time before completing a Promise.\n         */\n        timeout(ms) {\n            return new Promise(resolve => { setTimeout(() => resolve(), ms); });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * An error returned in rejected promises if the given key is not found in the table.\n     */\n    class NotFound {\n        constructor(table, key) {\n            this.table = table;\n            this.key = key;\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * An implementation of a `Database` that uses the `CacheStorage` API to serialize\n     * state within mock `Response` objects.\n     */\n    class CacheDatabase {\n        constructor(scope, adapter) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.tables = new Map();\n        }\n        'delete'(name) {\n            if (this.tables.has(name)) {\n                this.tables.delete(name);\n            }\n            return this.scope.caches.delete(`${this.adapter.cacheNamePrefix}:db:${name}`);\n        }\n        list() {\n            return this.scope.caches.keys().then(keys => keys.filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:db:`)));\n        }\n        open(name) {\n            if (!this.tables.has(name)) {\n                const table = this.scope.caches.open(`${this.adapter.cacheNamePrefix}:db:${name}`)\n                    .then(cache => new CacheTable(name, cache, this.adapter));\n                this.tables.set(name, table);\n            }\n            return this.tables.get(name);\n        }\n    }\n    /**\n     * A `Table` backed by a `Cache`.\n     */\n    class CacheTable {\n        constructor(table, cache, adapter) {\n            this.table = table;\n            this.cache = cache;\n            this.adapter = adapter;\n        }\n        request(key) { return this.adapter.newRequest('/' + key); }\n        'delete'(key) { return this.cache.delete(this.request(key)); }\n        keys() {\n            return this.cache.keys().then(requests => requests.map(req => req.url.substr(1)));\n        }\n        read(key) {\n            return this.cache.match(this.request(key)).then(res => {\n                if (res === undefined) {\n                    return Promise.reject(new NotFound(this.table, key));\n                }\n                return res.json();\n            });\n        }\n        write(key, value) {\n            return this.cache.put(this.request(key), this.adapter.newResponse(JSON.stringify(value)));\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var UpdateCacheStatus;\n    (function (UpdateCacheStatus) {\n        UpdateCacheStatus[UpdateCacheStatus[\"NOT_CACHED\"] = 0] = \"NOT_CACHED\";\n        UpdateCacheStatus[UpdateCacheStatus[\"CACHED_BUT_UNUSED\"] = 1] = \"CACHED_BUT_UNUSED\";\n        UpdateCacheStatus[UpdateCacheStatus[\"CACHED\"] = 2] = \"CACHED\";\n    })(UpdateCacheStatus || (UpdateCacheStatus = {}));\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    class SwCriticalError extends Error {\n        constructor() {\n            super(...arguments);\n            this.isCritical = true;\n        }\n    }\n    function errorToString(error) {\n        if (error instanceof Error) {\n            return `${error.message}\\n${error.stack}`;\n        }\n        else {\n            return `${error}`;\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * Compute the SHA1 of the given string\n     *\n     * see http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf\n     *\n     * WARNING: this function has not been designed not tested with security in mind.\n     *          DO NOT USE IT IN A SECURITY SENSITIVE CONTEXT.\n     *\n     * Borrowed from @angular/compiler/src/i18n/digest.ts\n     */\n    function sha1(str) {\n        const utf8 = str;\n        const words32 = stringToWords32(utf8, Endian.Big);\n        return _sha1(words32, utf8.length * 8);\n    }\n    function sha1Binary(buffer) {\n        const words32 = arrayBufferToWords32(buffer, Endian.Big);\n        return _sha1(words32, buffer.byteLength * 8);\n    }\n    function _sha1(words32, len) {\n        const w = [];\n        let [a, b, c, d, e] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0];\n        words32[len >> 5] |= 0x80 << (24 - len % 32);\n        words32[((len + 64 >> 9) << 4) + 15] = len;\n        for (let i = 0; i < words32.length; i += 16) {\n            const [h0, h1, h2, h3, h4] = [a, b, c, d, e];\n            for (let j = 0; j < 80; j++) {\n                if (j < 16) {\n                    w[j] = words32[i + j];\n                }\n                else {\n                    w[j] = rol32(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);\n                }\n                const [f, k] = fk(j, b, c, d);\n                const temp = [rol32(a, 5), f, e, k, w[j]].reduce(add32);\n                [e, d, c, b, a] = [d, c, rol32(b, 30), a, temp];\n            }\n            [a, b, c, d, e] = [add32(a, h0), add32(b, h1), add32(c, h2), add32(d, h3), add32(e, h4)];\n        }\n        return byteStringToHexString(words32ToByteString([a, b, c, d, e]));\n    }\n    function add32(a, b) {\n        return add32to64(a, b)[1];\n    }\n    function add32to64(a, b) {\n        const low = (a & 0xffff) + (b & 0xffff);\n        const high = (a >>> 16) + (b >>> 16) + (low >>> 16);\n        return [high >>> 16, (high << 16) | (low & 0xffff)];\n    }\n    // Rotate a 32b number left `count` position\n    function rol32(a, count) {\n        return (a << count) | (a >>> (32 - count));\n    }\n    var Endian;\n    (function (Endian) {\n        Endian[Endian[\"Little\"] = 0] = \"Little\";\n        Endian[Endian[\"Big\"] = 1] = \"Big\";\n    })(Endian || (Endian = {}));\n    function fk(index, b, c, d) {\n        if (index < 20) {\n            return [(b & c) | (~b & d), 0x5a827999];\n        }\n        if (index < 40) {\n            return [b ^ c ^ d, 0x6ed9eba1];\n        }\n        if (index < 60) {\n            return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];\n        }\n        return [b ^ c ^ d, 0xca62c1d6];\n    }\n    function stringToWords32(str, endian) {\n        const size = (str.length + 3) >>> 2;\n        const words32 = [];\n        for (let i = 0; i < size; i++) {\n            words32[i] = wordAt(str, i * 4, endian);\n        }\n        return words32;\n    }\n    function arrayBufferToWords32(buffer, endian) {\n        const size = (buffer.byteLength + 3) >>> 2;\n        const words32 = [];\n        const view = new Uint8Array(buffer);\n        for (let i = 0; i < size; i++) {\n            words32[i] = wordAt(view, i * 4, endian);\n        }\n        return words32;\n    }\n    function byteAt(str, index) {\n        if (typeof str === 'string') {\n            return index >= str.length ? 0 : str.charCodeAt(index) & 0xff;\n        }\n        else {\n            return index >= str.byteLength ? 0 : str[index] & 0xff;\n        }\n    }\n    function wordAt(str, index, endian) {\n        let word = 0;\n        if (endian === Endian.Big) {\n            for (let i = 0; i < 4; i++) {\n                word += byteAt(str, index + i) << (24 - 8 * i);\n            }\n        }\n        else {\n            for (let i = 0; i < 4; i++) {\n                word += byteAt(str, index + i) << 8 * i;\n            }\n        }\n        return word;\n    }\n    function words32ToByteString(words32) {\n        return words32.reduce((str, word) => str + word32ToByteString(word), '');\n    }\n    function word32ToByteString(word) {\n        let str = '';\n        for (let i = 0; i < 4; i++) {\n            str += String.fromCharCode((word >>> 8 * (3 - i)) & 0xff);\n        }\n        return str;\n    }\n    function byteStringToHexString(str) {\n        let hex = '';\n        for (let i = 0; i < str.length; i++) {\n            const b = byteAt(str, i);\n            hex += (b >>> 4).toString(16) + (b & 0x0f).toString(16);\n        }\n        return hex.toLowerCase();\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    /**\n     * A group of assets that are cached in a `Cache` and managed by a given policy.\n     *\n     * Concrete classes derive from this base and specify the exact caching policy.\n     */\n    class AssetGroup {\n        constructor(scope, adapter, idle, config, hashes, db, prefix) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.idle = idle;\n            this.config = config;\n            this.hashes = hashes;\n            this.db = db;\n            this.prefix = prefix;\n            /**\n             * A deduplication cache, to make sure the SW never makes two network requests\n             * for the same resource at once. Managed by `fetchAndCacheOnce`.\n             */\n            this.inFlightRequests = new Map();\n            /**\n             * Regular expression patterns.\n             */\n            this.patterns = [];\n            this.name = config.name;\n            // Patterns in the config are regular expressions disguised as strings. Breathe life into them.\n            this.patterns = this.config.patterns.map(pattern => new RegExp(pattern));\n            // This is the primary cache, which holds all of the cached requests for this group. If a\n            // resource\n            // isn't in this cache, it hasn't been fetched yet.\n            this.cache = this.scope.caches.open(`${this.prefix}:${this.config.name}:cache`);\n            // This is the metadata table, which holds specific information for each cached URL, such as\n            // the timestamp of when it was added to the cache.\n            this.metadata = this.db.open(`${this.prefix}:${this.config.name}:meta`);\n            // Determine the origin from the registration scope. This is used to differentiate between\n            // relative and absolute URLs.\n            this.origin = this.adapter.parseUrl(this.scope.registration.scope).origin;\n        }\n        cacheStatus(url) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                const meta = yield this.metadata;\n                const res = yield cache.match(this.adapter.newRequest(url));\n                if (res === undefined) {\n                    return UpdateCacheStatus.NOT_CACHED;\n                }\n                try {\n                    const data = yield meta.read(url);\n                    if (!data.used) {\n                        return UpdateCacheStatus.CACHED_BUT_UNUSED;\n                    }\n                }\n                catch (_) {\n                    // Error on the side of safety and assume cached.\n                }\n                return UpdateCacheStatus.CACHED;\n            });\n        }\n        /**\n         * Clean up all the cached data for this group.\n         */\n        cleanup() {\n            return __awaiter(this, void 0, void 0, function* () {\n                yield this.scope.caches.delete(`${this.prefix}:${this.config.name}:cache`);\n                yield this.db.delete(`${this.prefix}:${this.config.name}:meta`);\n            });\n        }\n        /**\n         * Process a request for a given resource and return it, or return null if it's not available.\n         */\n        handleFetch(req, ctx) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                // Either the request matches one of the known resource URLs, one of the patterns for\n                // dynamically matched URLs, or neither. Determine which is the case for this request in\n                // order to decide how to handle it.\n                if (this.config.urls.indexOf(url) !== -1 || this.patterns.some(pattern => pattern.test(url))) {\n                    // This URL matches a known resource. Either it's been cached already or it's missing, in\n                    // which case it needs to be loaded from the network.\n                    // Open the cache to check whether this resource is present.\n                    const cache = yield this.cache;\n                    // Look for a cached response. If one exists, it can be used to resolve the fetch\n                    // operation.\n                    const cachedResponse = yield cache.match(req);\n                    if (cachedResponse !== undefined) {\n                        // A response has already been cached (which presumably matches the hash for this\n                        // resource). Check whether it's safe to serve this resource from cache.\n                        if (this.hashes.has(url)) {\n                            // This resource has a hash, and thus is versioned by the manifest. It's safe to return\n                            // the response.\n                            return cachedResponse;\n                        }\n                        else {\n                            // This resource has no hash, and yet exists in the cache. Check how old this request is\n                            // to make sure it's still usable.\n                            if (yield this.needToRevalidate(req, cachedResponse)) {\n                                this.idle.schedule(`revalidate(${this.prefix}, ${this.config.name}): ${req.url}`, () => __awaiter(this, void 0, void 0, function* () { yield this.fetchAndCacheOnce(req); }));\n                            }\n                            // In either case (revalidation or not), the cached response must be good.\n                            return cachedResponse;\n                        }\n                    }\n                    // No already-cached response exists, so attempt a fetch/cache operation. The original request\n                    // may specify things like credential inclusion, but for assets these are not honored in order\n                    // to avoid issues with opaque responses. The SW requests the data itself.\n                    const res = yield this.fetchAndCacheOnce(this.adapter.newRequest(req.url));\n                    // If this is successful, the response needs to be cloned as it might be used to respond to\n                    // multiple fetch operations at the same time.\n                    return res.clone();\n                }\n                else {\n                    return null;\n                }\n            });\n        }\n        getConfigUrl(url) {\n            // If the URL is relative to the SW's own origin, then only consider the path relative to\n            // the domain root. Determine this by checking the URL's origin against the SW's.\n            const parsed = this.adapter.parseUrl(url, this.scope.registration.scope);\n            if (parsed.origin === this.origin) {\n                // The URL is relative to the SW's origin domain.\n                return parsed.path;\n            }\n            else {\n                return url;\n            }\n        }\n        /**\n         * Some resources are cached without a hash, meaning that their expiration is controlled\n         * by HTTP caching headers. Check whether the given request/response pair is still valid\n         * per the caching headers.\n         */\n        needToRevalidate(req, res) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Three different strategies apply here:\n                // 1) The request has a Cache-Control header, and thus expiration needs to be based on its age.\n                // 2) The request has an Expires header, and expiration is based on the current timestamp.\n                // 3) The request has no applicable caching headers, and must be revalidated.\n                if (res.headers.has('Cache-Control')) {\n                    // Figure out if there is a max-age directive in the Cache-Control header.\n                    const cacheControl = res.headers.get('Cache-Control');\n                    const cacheDirectives = cacheControl\n                        // Directives are comma-separated within the Cache-Control header value.\n                        .split(',')\n                        // Make sure each directive doesn't have extraneous whitespace.\n                        .map(v => v.trim())\n                        // Some directives have values (like maxage and s-maxage)\n                        .map(v => v.split('='));\n                    // Lowercase all the directive names.\n                    cacheDirectives.forEach(v => v[0] = v[0].toLowerCase());\n                    // Find the max-age directive, if one exists.\n                    const maxAgeDirective = cacheDirectives.find(v => v[0] === 'max-age');\n                    const cacheAge = maxAgeDirective ? maxAgeDirective[1] : undefined;\n                    if (!cacheAge) {\n                        // No usable TTL defined. Must assume that the response is stale.\n                        return true;\n                    }\n                    try {\n                        const maxAge = 1000 * parseInt(cacheAge);\n                        // Determine the origin time of this request. If the SW has metadata on the request (which\n                        // it\n                        // should), it will have the time the request was added to the cache. If it doesn't for some\n                        // reason, the request may have a Date header which will serve the same purpose.\n                        let ts;\n                        try {\n                            // Check the metadata table. If a timestamp is there, use it.\n                            const metaTable = yield this.metadata;\n                            ts = (yield metaTable.read(req.url)).ts;\n                        }\n                        catch (_a) {\n                            // Otherwise, look for a Date header.\n                            const date = res.headers.get('Date');\n                            if (date === null) {\n                                // Unable to determine when this response was created. Assume that it's stale, and\n                                // revalidate it.\n                                return true;\n                            }\n                            ts = Date.parse(date);\n                        }\n                        const age = this.adapter.time - ts;\n                        return age < 0 || age > maxAge;\n                    }\n                    catch (_b) {\n                        // Assume stale.\n                        return true;\n                    }\n                }\n                else if (res.headers.has('Expires')) {\n                    // Determine if the expiration time has passed.\n                    const expiresStr = res.headers.get('Expires');\n                    try {\n                        // The request needs to be revalidated if the current time is later than the expiration\n                        // time, if it parses correctly.\n                        return this.adapter.time > Date.parse(expiresStr);\n                    }\n                    catch (_c) {\n                        // The expiration date failed to parse, so revalidate as a precaution.\n                        return true;\n                    }\n                }\n                else {\n                    // No way to evaluate staleness, so assume the response is already stale.\n                    return true;\n                }\n            });\n        }\n        /**\n         * Fetch the complete state of a cached resource, or return null if it's not found.\n         */\n        fetchFromCacheOnly(url) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                const metaTable = yield this.metadata;\n                // Lookup the response in the cache.\n                const response = yield cache.match(this.adapter.newRequest(url));\n                if (response === undefined) {\n                    // It's not found, return null.\n                    return null;\n                }\n                // Next, lookup the cached metadata.\n                let metadata = undefined;\n                try {\n                    metadata = yield metaTable.read(url);\n                }\n                catch (_a) {\n                    // Do nothing, not found. This shouldn't happen, but it can be handled.\n                }\n                // Return both the response and any available metadata.\n                return { response, metadata };\n            });\n        }\n        /**\n         * Lookup all resources currently stored in the cache which have no associated hash.\n         */\n        unhashedResources() {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                // Start with the set of all cached URLs.\n                return (yield cache.keys())\n                    .map(request => request.url)\n                    // Exclude the URLs which have hashes.\n                    .filter(url => !this.hashes.has(url));\n            });\n        }\n        /**\n         * Fetch the given resource from the network, and cache it if able.\n         */\n        fetchAndCacheOnce(req, used = true) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // The `inFlightRequests` map holds information about which caching operations are currently\n                // underway for known resources. If this request appears there, another \"thread\" is already\n                // in the process of caching it, and this work should not be duplicated.\n                if (this.inFlightRequests.has(req.url)) {\n                    // There is a caching operation already in progress for this request. Wait for it to\n                    // complete, and hopefully it will have yielded a useful response.\n                    return this.inFlightRequests.get(req.url);\n                }\n                // No other caching operation is being attempted for this resource, so it will be owned here.\n                // Go to the network and get the correct version.\n                const fetchOp = this.fetchFromNetwork(req);\n                // Save this operation in `inFlightRequests` so any other \"thread\" attempting to cache it\n                // will block on this chain instead of duplicating effort.\n                this.inFlightRequests.set(req.url, fetchOp);\n                // Make sure this attempt is cleaned up properly on failure.\n                try {\n                    // Wait for a response. If this fails, the request will remain in `inFlightRequests`\n                    // indefinitely.\n                    const res = yield fetchOp;\n                    // It's very important that only successful responses are cached. Unsuccessful responses\n                    // should never be cached as this can completely break applications.\n                    if (!res.ok) {\n                        throw new Error(`Response not Ok (fetchAndCacheOnce): request for ${req.url} returned response ${res.status} ${res.statusText}`);\n                    }\n                    try {\n                        // This response is safe to cache (as long as it's cloned). Wait until the cache operation\n                        // is complete.\n                        const cache = yield this.scope.caches.open(`${this.prefix}:${this.config.name}:cache`);\n                        yield cache.put(req, res.clone());\n                        // If the request is not hashed, update its metadata, especially the timestamp. This is\n                        // needed for future determination of whether this cached response is stale or not.\n                        if (!this.hashes.has(req.url)) {\n                            // Metadata is tracked for requests that are unhashed.\n                            const meta = { ts: this.adapter.time, used };\n                            const metaTable = yield this.metadata;\n                            yield metaTable.write(req.url, meta);\n                        }\n                        return res;\n                    }\n                    catch (err) {\n                        // Among other cases, this can happen when the user clears all data through the DevTools,\n                        // but the SW is still running and serving another tab. In that case, trying to write to the\n                        // caches throws an `Entry was not found` error.\n                        // If this happens the SW can no longer work correctly. This situation is unrecoverable.\n                        throw new SwCriticalError(`Failed to update the caches for request to '${req.url}' (fetchAndCacheOnce): ${errorToString(err)}`);\n                    }\n                }\n                finally {\n                    // Finally, it can be removed from `inFlightRequests`. This might result in a double-remove\n                    // if some other chain was already making this request too, but that won't hurt anything.\n                    this.inFlightRequests.delete(req.url);\n                }\n            });\n        }\n        fetchFromNetwork(req, redirectLimit = 3) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Make a cache-busted request for the resource.\n                const res = yield this.cacheBustedFetchFromNetwork(req);\n                // Check for redirected responses, and follow the redirects.\n                if (res['redirected'] && !!res.url) {\n                    // If the redirect limit is exhausted, fail with an error.\n                    if (redirectLimit === 0) {\n                        throw new SwCriticalError(`Response hit redirect limit (fetchFromNetwork): request redirected too many times, next is ${res.url}`);\n                    }\n                    // Unwrap the redirect directly.\n                    return this.fetchFromNetwork(this.adapter.newRequest(res.url), redirectLimit - 1);\n                }\n                return res;\n            });\n        }\n        /**\n         * Load a particular asset from the network, accounting for hash validation.\n         */\n        cacheBustedFetchFromNetwork(req) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                // If a hash is available for this resource, then compare the fetched version with the\n                // canonical hash. Otherwise, the network version will have to be trusted.\n                if (this.hashes.has(url)) {\n                    // It turns out this resource does have a hash. Look it up. Unless the fetched version\n                    // matches this hash, it's invalid and the whole manifest may need to be thrown out.\n                    const canonicalHash = this.hashes.get(url);\n                    // Ideally, the resource would be requested with cache-busting to guarantee the SW gets\n                    // the freshest version. However, doing this would eliminate any chance of the response\n                    // being in the HTTP cache. Given that the browser has recently actively loaded the page,\n                    // it's likely that many of the responses the SW needs to cache are in the HTTP cache and\n                    // are fresh enough to use. In the future, this could be done by setting cacheMode to\n                    // *only* check the browser cache for a cached version of the resource, when cacheMode is\n                    // fully supported. For now, the resource is fetched directly, without cache-busting, and\n                    // if the hash test fails a cache-busted request is tried before concluding that the\n                    // resource isn't correct. This gives the benefit of acceleration via the HTTP cache\n                    // without the risk of stale data, at the expense of a duplicate request in the event of\n                    // a stale response.\n                    // Fetch the resource from the network (possibly hitting the HTTP cache).\n                    const networkResult = yield this.safeFetch(req);\n                    // Decide whether a cache-busted request is necessary. It might be for two independent\n                    // reasons: either the non-cache-busted request failed (hopefully transiently) or if the\n                    // hash of the content retrieved does not match the canonical hash from the manifest. It's\n                    // only valid to access the content of the first response if the request was successful.\n                    let makeCacheBustedRequest = networkResult.ok;\n                    if (makeCacheBustedRequest) {\n                        // The request was successful. A cache-busted request is only necessary if the hashes\n                        // don't match. Compare them, making sure to clone the response so it can be used later\n                        // if it proves to be valid.\n                        const fetchedHash = sha1Binary(yield networkResult.clone().arrayBuffer());\n                        makeCacheBustedRequest = (fetchedHash !== canonicalHash);\n                    }\n                    // Make a cache busted request to the network, if necessary.\n                    if (makeCacheBustedRequest) {\n                        // Hash failure, the version that was retrieved under the default URL did not have the\n                        // hash expected. This could be because the HTTP cache got in the way and returned stale\n                        // data, or because the version on the server really doesn't match. A cache-busting\n                        // request will differentiate these two situations.\n                        // TODO: handle case where the URL has parameters already (unlikely for assets).\n                        const cacheBustReq = this.adapter.newRequest(this.cacheBust(req.url));\n                        const cacheBustedResult = yield this.safeFetch(cacheBustReq);\n                        // If the response was unsuccessful, there's nothing more that can be done.\n                        if (!cacheBustedResult.ok) {\n                            throw new SwCriticalError(`Response not Ok (cacheBustedFetchFromNetwork): cache busted request for ${req.url} returned response ${cacheBustedResult.status} ${cacheBustedResult.statusText}`);\n                        }\n                        // Hash the contents.\n                        const cacheBustedHash = sha1Binary(yield cacheBustedResult.clone().arrayBuffer());\n                        // If the cache-busted version doesn't match, then the manifest is not an accurate\n                        // representation of the server's current set of files, and the SW should give up.\n                        if (canonicalHash !== cacheBustedHash) {\n                            throw new SwCriticalError(`Hash mismatch (cacheBustedFetchFromNetwork): ${req.url}: expected ${canonicalHash}, got ${cacheBustedHash} (after cache busting)`);\n                        }\n                        // If it does match, then use the cache-busted result.\n                        return cacheBustedResult;\n                    }\n                    // Excellent, the version from the network matched on the first try, with no need for\n                    // cache-busting. Use it.\n                    return networkResult;\n                }\n                else {\n                    // This URL doesn't exist in our hash database, so it must be requested directly.\n                    return this.safeFetch(req);\n                }\n            });\n        }\n        /**\n         * Possibly update a resource, if it's expired and needs to be updated. A no-op otherwise.\n         */\n        maybeUpdate(updateFrom, req, cache) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                const meta = yield this.metadata;\n                // Check if this resource is hashed and already exists in the cache of a prior version.\n                if (this.hashes.has(url)) {\n                    const hash = this.hashes.get(url);\n                    // Check the caches of prior versions, using the hash to ensure the correct version of\n                    // the resource is loaded.\n                    const res = yield updateFrom.lookupResourceWithHash(url, hash);\n                    // If a previously cached version was available, copy it over to this cache.\n                    if (res !== null) {\n                        // Copy to this cache.\n                        yield cache.put(req, res);\n                        yield meta.write(req.url, { ts: this.adapter.time, used: false });\n                        // No need to do anything further with this resource, it's now cached properly.\n                        return true;\n                    }\n                }\n                // No up-to-date version of this resource could be found.\n                return false;\n            });\n        }\n        /**\n         * Construct a cache-busting URL for a given URL.\n         */\n        cacheBust(url) {\n            return url + (url.indexOf('?') === -1 ? '?' : '&') + 'ngsw-cache-bust=' + Math.random();\n        }\n        safeFetch(req) {\n            return __awaiter(this, void 0, void 0, function* () {\n                try {\n                    return yield this.scope.fetch(req);\n                }\n                catch (_a) {\n                    return this.adapter.newResponse('', {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n    /**\n     * An `AssetGroup` that prefetches all of its resources during initialization.\n     */\n    class PrefetchAssetGroup extends AssetGroup {\n        initializeFully(updateFrom) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Open the cache which actually holds requests.\n                const cache = yield this.cache;\n                // Cache all known resources serially. As this reduce proceeds, each Promise waits\n                // on the last before starting the fetch/cache operation for the next request. Any\n                // errors cause fall-through to the final Promise which rejects.\n                yield this.config.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                    // Wait on all previous operations to complete.\n                    yield previous;\n                    // Construct the Request for this url.\n                    const req = this.adapter.newRequest(url);\n                    // First, check the cache to see if there is already a copy of this resource.\n                    const alreadyCached = (yield cache.match(req)) !== undefined;\n                    // If the resource is in the cache already, it can be skipped.\n                    if (alreadyCached) {\n                        return;\n                    }\n                    // If an update source is available.\n                    if (updateFrom !== undefined && (yield this.maybeUpdate(updateFrom, req, cache))) {\n                        return;\n                    }\n                    // Otherwise, go to the network and hopefully cache the response (if successful).\n                    yield this.fetchAndCacheOnce(req, false);\n                }), Promise.resolve());\n                // Handle updating of unknown (unhashed) resources. This is only possible if there's\n                // a source to update from.\n                if (updateFrom !== undefined) {\n                    const metaTable = yield this.metadata;\n                    // Select all of the previously cached resources. These are cached unhashed resources\n                    // from previous versions of the app, in any asset group.\n                    yield (yield updateFrom.previouslyCachedResources())\n                        // First, narrow down the set of resources to those which are handled by this group.\n                        // Either it's a known URL, or it matches a given pattern.\n                        .filter(url => this.config.urls.some(cacheUrl => cacheUrl === url) ||\n                        this.patterns.some(pattern => pattern.test(url)))\n                        // Finally, process each resource in turn.\n                        .reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                        yield previous;\n                        const req = this.adapter.newRequest(url);\n                        // It's possible that the resource in question is already cached. If so,\n                        // continue to the next one.\n                        const alreadyCached = ((yield cache.match(req)) !== undefined);\n                        if (alreadyCached) {\n                            return;\n                        }\n                        // Get the most recent old version of the resource.\n                        const res = yield updateFrom.lookupResourceWithoutHash(url);\n                        if (res === null || res.metadata === undefined) {\n                            // Unexpected, but not harmful.\n                            return;\n                        }\n                        // Write it into the cache. It may already be expired, but it can still serve\n                        // traffic until it's updated (stale-while-revalidate approach).\n                        yield cache.put(req, res.response);\n                        yield metaTable.write(url, Object.assign(Object.assign({}, res.metadata), { used: false }));\n                    }), Promise.resolve());\n                }\n            });\n        }\n    }\n    class LazyAssetGroup extends AssetGroup {\n        initializeFully(updateFrom) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // No action necessary if no update source is available - resources managed in this group\n                // are all lazily loaded, so there's nothing to initialize.\n                if (updateFrom === undefined) {\n                    return;\n                }\n                // Open the cache which actually holds requests.\n                const cache = yield this.cache;\n                // Loop through the listed resources, caching any which are available.\n                yield this.config.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                    // Wait on all previous operations to complete.\n                    yield previous;\n                    // Construct the Request for this url.\n                    const req = this.adapter.newRequest(url);\n                    // First, check the cache to see if there is already a copy of this resource.\n                    const alreadyCached = (yield cache.match(req)) !== undefined;\n                    // If the resource is in the cache already, it can be skipped.\n                    if (alreadyCached) {\n                        return;\n                    }\n                    const updated = yield this.maybeUpdate(updateFrom, req, cache);\n                    if (this.config.updateMode === 'prefetch' && !updated) {\n                        // If the resource was not updated, either it was not cached before or\n                        // the previously cached version didn't match the updated hash. In that\n                        // case, prefetch update mode dictates that the resource will be updated,\n                        // except if it was not previously utilized. Check the status of the\n                        // cached resource to see.\n                        const cacheStatus = yield updateFrom.recentCacheStatus(url);\n                        // If the resource is not cached, or was cached but unused, then it will be\n                        // loaded lazily.\n                        if (cacheStatus !== UpdateCacheStatus.CACHED) {\n                            return;\n                        }\n                        // Update from the network.\n                        yield this.fetchAndCacheOnce(req, false);\n                    }\n                }), Promise.resolve());\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    /**\n     * Manages an instance of `LruState` and moves URLs to the head of the\n     * chain when requested.\n     */\n    class LruList {\n        constructor(state) {\n            if (state === undefined) {\n                state = {\n                    head: null,\n                    tail: null,\n                    map: {},\n                    count: 0,\n                };\n            }\n            this.state = state;\n        }\n        /**\n         * The current count of URLs in the list.\n         */\n        get size() { return this.state.count; }\n        /**\n         * Remove the tail.\n         */\n        pop() {\n            // If there is no tail, return null.\n            if (this.state.tail === null) {\n                return null;\n            }\n            const url = this.state.tail;\n            this.remove(url);\n            // This URL has been successfully evicted.\n            return url;\n        }\n        remove(url) {\n            const node = this.state.map[url];\n            if (node === undefined) {\n                return false;\n            }\n            // Special case if removing the current head.\n            if (this.state.head === url) {\n                // The node is the current head. Special case the removal.\n                if (node.next === null) {\n                    // This is the only node. Reset the cache to be empty.\n                    this.state.head = null;\n                    this.state.tail = null;\n                    this.state.map = {};\n                    this.state.count = 0;\n                    return true;\n                }\n                // There is at least one other node. Make the next node the new head.\n                const next = this.state.map[node.next];\n                next.previous = null;\n                this.state.head = next.url;\n                node.next = null;\n                delete this.state.map[url];\n                this.state.count--;\n                return true;\n            }\n            // The node is not the head, so it has a previous. It may or may not be the tail.\n            // If it is not, then it has a next. First, grab the previous node.\n            const previous = this.state.map[node.previous];\n            // Fix the forward pointer to skip over node and go directly to node.next.\n            previous.next = node.next;\n            // node.next may or may not be set. If it is, fix the back pointer to skip over node.\n            // If it's not set, then this node happened to be the tail, and the tail needs to be\n            // updated to point to the previous node (removing the tail).\n            if (node.next !== null) {\n                // There is a next node, fix its back pointer to skip this node.\n                this.state.map[node.next].previous = node.previous;\n            }\n            else {\n                // There is no next node - the accessed node must be the tail. Move the tail pointer.\n                this.state.tail = node.previous;\n            }\n            node.next = null;\n            node.previous = null;\n            delete this.state.map[url];\n            // Count the removal.\n            this.state.count--;\n            return true;\n        }\n        accessed(url) {\n            // When a URL is accessed, its node needs to be moved to the head of the chain.\n            // This is accomplished in two steps:\n            //\n            // 1) remove the node from its position within the chain.\n            // 2) insert the node as the new head.\n            //\n            // Sometimes, a URL is accessed which has not been seen before. In this case, step 1 can\n            // be skipped completely (which will grow the chain by one). Of course, if the node is\n            // already the head, this whole operation can be skipped.\n            if (this.state.head === url) {\n                // The URL is already in the head position, accessing it is a no-op.\n                return;\n            }\n            // Look up the node in the map, and construct a new entry if it's\n            const node = this.state.map[url] || { url, next: null, previous: null };\n            // Step 1: remove the node from its position within the chain, if it is in the chain.\n            if (this.state.map[url] !== undefined) {\n                this.remove(url);\n            }\n            // Step 2: insert the node at the head of the chain.\n            // First, check if there's an existing head node. If there is, it has previous: null.\n            // Its previous pointer should be set to the node we're inserting.\n            if (this.state.head !== null) {\n                this.state.map[this.state.head].previous = url;\n            }\n            // The next pointer of the node being inserted gets set to the old head, before the head\n            // pointer is updated to this node.\n            node.next = this.state.head;\n            // The new head is the new node.\n            this.state.head = url;\n            // If there is no tail, then this is the first node, and is both the head and the tail.\n            if (this.state.tail === null) {\n                this.state.tail = url;\n            }\n            // Set the node in the map of nodes (if the URL has been seen before, this is a no-op)\n            // and count the insertion.\n            this.state.map[url] = node;\n            this.state.count++;\n        }\n    }\n    /**\n     * A group of cached resources determined by a set of URL patterns which follow a LRU policy\n     * for caching.\n     */\n    class DataGroup {\n        constructor(scope, adapter, config, db, debugHandler, prefix) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.config = config;\n            this.db = db;\n            this.debugHandler = debugHandler;\n            this.prefix = prefix;\n            /**\n             * Tracks the LRU state of resources in this cache.\n             */\n            this._lru = null;\n            this.patterns = this.config.patterns.map(pattern => new RegExp(pattern));\n            this.cache = this.scope.caches.open(`${this.prefix}:dynamic:${this.config.name}:cache`);\n            this.lruTable = this.db.open(`${this.prefix}:dynamic:${this.config.name}:lru`);\n            this.ageTable = this.db.open(`${this.prefix}:dynamic:${this.config.name}:age`);\n        }\n        /**\n         * Lazily initialize/load the LRU chain.\n         */\n        lru() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                if (this._lru === null) {\n                    const table = yield this.lruTable;\n                    try {\n                        this._lru = new LruList(yield table.read('lru'));\n                    }\n                    catch (_a) {\n                        this._lru = new LruList();\n                    }\n                }\n                return this._lru;\n            });\n        }\n        /**\n         * Sync the LRU chain to non-volatile storage.\n         */\n        syncLru() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                if (this._lru === null) {\n                    return;\n                }\n                const table = yield this.lruTable;\n                try {\n                    return table.write('lru', this._lru.state);\n                }\n                catch (err) {\n                    // Writing lru cache table failed. This could be a result of a full storage.\n                    // Continue serving clients as usual.\n                    this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).syncLru()`);\n                    // TODO: Better detect/handle full storage; e.g. using\n                    // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).\n                }\n            });\n        }\n        /**\n         * Process a fetch event and return a `Response` if the resource is covered by this group,\n         * or `null` otherwise.\n         */\n        handleFetch(req, ctx) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Do nothing\n                if (!this.patterns.some(pattern => pattern.test(req.url))) {\n                    return null;\n                }\n                // Lazily initialize the LRU cache.\n                const lru = yield this.lru();\n                // The URL matches this cache. First, check whether this is a mutating request or not.\n                switch (req.method) {\n                    case 'OPTIONS':\n                        // Don't try to cache this - it's non-mutating, but is part of a mutating request.\n                        // Most likely SWs don't even see this, but this guard is here just in case.\n                        return null;\n                    case 'GET':\n                    case 'HEAD':\n                        // Handle the request with whatever strategy was selected.\n                        switch (this.config.strategy) {\n                            case 'freshness':\n                                return this.handleFetchWithFreshness(req, ctx, lru);\n                            case 'performance':\n                                return this.handleFetchWithPerformance(req, ctx, lru);\n                            default:\n                                throw new Error(`Unknown strategy: ${this.config.strategy}`);\n                        }\n                    default:\n                        // This was a mutating request. Assume the cache for this URL is no longer valid.\n                        const wasCached = lru.remove(req.url);\n                        // If there was a cached entry, remove it.\n                        if (wasCached) {\n                            yield this.clearCacheForUrl(req.url);\n                        }\n                        // Sync the LRU chain to non-volatile storage.\n                        yield this.syncLru();\n                        // Finally, fall back on the network.\n                        return this.safeFetch(req);\n                }\n            });\n        }\n        handleFetchWithPerformance(req, ctx, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                let res = null;\n                // Check the cache first. If the resource exists there (and is not expired), the cached\n                // version can be used.\n                const fromCache = yield this.loadFromCache(req, lru);\n                if (fromCache !== null) {\n                    res = fromCache.res;\n                    // Check the age of the resource.\n                    if (this.config.refreshAheadMs !== undefined && fromCache.age >= this.config.refreshAheadMs) {\n                        ctx.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru));\n                    }\n                }\n                if (res !== null) {\n                    return res;\n                }\n                // No match from the cache. Go to the network. Note that this is not an 'await'\n                // call, networkFetch is the actual Promise. This is due to timeout handling.\n                const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);\n                res = yield timeoutFetch;\n                // Since fetch() will always return a response, undefined indicates a timeout.\n                if (res === undefined) {\n                    // The request timed out. Return a Gateway Timeout error.\n                    res = this.adapter.newResponse(null, { status: 504, statusText: 'Gateway Timeout' });\n                    // Cache the network response eventually.\n                    ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru));\n                }\n                else {\n                    // The request completed in time, so cache it inline with the response flow.\n                    yield this.safeCacheResponse(req, res, lru);\n                }\n                return res;\n            });\n        }\n        handleFetchWithFreshness(req, ctx, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Start with a network fetch.\n                const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);\n                let res;\n                // If that fetch errors, treat it as a timed out request.\n                try {\n                    res = yield timeoutFetch;\n                }\n                catch (_a) {\n                    res = undefined;\n                }\n                // If the network fetch times out or errors, fall back on the cache.\n                if (res === undefined) {\n                    ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true));\n                    // Ignore the age, the network response will be cached anyway due to the\n                    // behavior of freshness.\n                    const fromCache = yield this.loadFromCache(req, lru);\n                    res = (fromCache !== null) ? fromCache.res : null;\n                }\n                else {\n                    yield this.safeCacheResponse(req, res, lru, true);\n                }\n                // Either the network fetch didn't time out, or the cache yielded a usable response.\n                // In either case, use it.\n                if (res !== null) {\n                    return res;\n                }\n                // No response in the cache. No choice but to fall back on the full network fetch.\n                return networkFetch;\n            });\n        }\n        networkFetchWithTimeout(req) {\n            // If there is a timeout configured, race a timeout Promise with the network fetch.\n            // Otherwise, just fetch from the network directly.\n            if (this.config.timeoutMs !== undefined) {\n                const networkFetch = this.scope.fetch(req);\n                const safeNetworkFetch = (() => __awaiter$1(this, void 0, void 0, function* () {\n                    try {\n                        return yield networkFetch;\n                    }\n                    catch (_a) {\n                        return this.adapter.newResponse(null, {\n                            status: 504,\n                            statusText: 'Gateway Timeout',\n                        });\n                    }\n                }))();\n                const networkFetchUndefinedError = (() => __awaiter$1(this, void 0, void 0, function* () {\n                    try {\n                        return yield networkFetch;\n                    }\n                    catch (_b) {\n                        return undefined;\n                    }\n                }))();\n                // Construct a Promise<undefined> for the timeout.\n                const timeout = this.adapter.timeout(this.config.timeoutMs);\n                // Race that with the network fetch. This will either be a Response, or `undefined`\n                // in the event that the request errored or timed out.\n                return [Promise.race([networkFetchUndefinedError, timeout]), safeNetworkFetch];\n            }\n            else {\n                const networkFetch = this.safeFetch(req);\n                // Do a plain fetch.\n                return [networkFetch, networkFetch];\n            }\n        }\n        safeCacheResponse(req, resOrPromise, lru, okToCacheOpaque) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                try {\n                    const res = yield resOrPromise;\n                    try {\n                        yield this.cacheResponse(req, res, lru, okToCacheOpaque);\n                    }\n                    catch (err) {\n                        // Saving the API response failed. This could be a result of a full storage.\n                        // Since this data is cached lazily and temporarily, continue serving clients as usual.\n                        this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).safeCacheResponse(${req.url}, status: ${res.status})`);\n                        // TODO: Better detect/handle full storage; e.g. using\n                        // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).\n                    }\n                }\n                catch (_a) {\n                    // Request failed\n                    // TODO: Handle this error somehow?\n                }\n            });\n        }\n        loadFromCache(req, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Look for a response in the cache. If one exists, return it.\n                const cache = yield this.cache;\n                let res = yield cache.match(req);\n                if (res !== undefined) {\n                    // A response was found in the cache, but its age is not yet known. Look it up.\n                    try {\n                        const ageTable = yield this.ageTable;\n                        const age = this.adapter.time - (yield ageTable.read(req.url)).age;\n                        // If the response is young enough, use it.\n                        if (age <= this.config.maxAge) {\n                            // Successful match from the cache. Use the response, after marking it as having\n                            // been accessed.\n                            lru.accessed(req.url);\n                            return { res, age };\n                        }\n                        // Otherwise, or if there was an error, assume the response is expired, and evict it.\n                    }\n                    catch (_a) {\n                        // Some error getting the age for the response. Assume it's expired.\n                    }\n                    lru.remove(req.url);\n                    yield this.clearCacheForUrl(req.url);\n                    // TODO: avoid duplicate in event of network timeout, maybe.\n                    yield this.syncLru();\n                }\n                return null;\n            });\n        }\n        /**\n         * Operation for caching the response from the server. This has to happen all\n         * at once, so that the cache and LRU tracking remain in sync. If the network request\n         * completes before the timeout, this logic will be run inline with the response flow.\n         * If the request times out on the server, an error will be returned but the real network\n         * request will still be running in the background, to be cached when it completes.\n         */\n        cacheResponse(req, res, lru, okToCacheOpaque = false) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Only cache successful responses.\n                if (!(res.ok || (okToCacheOpaque && res.type === 'opaque'))) {\n                    return;\n                }\n                // If caching this response would make the cache exceed its maximum size, evict something\n                // first.\n                if (lru.size >= this.config.maxSize) {\n                    // The cache is too big, evict something.\n                    const evictedUrl = lru.pop();\n                    if (evictedUrl !== null) {\n                        yield this.clearCacheForUrl(evictedUrl);\n                    }\n                }\n                // TODO: evaluate for possible race conditions during flaky network periods.\n                // Mark this resource as having been accessed recently. This ensures it won't be evicted\n                // until enough other resources are requested that it falls off the end of the LRU chain.\n                lru.accessed(req.url);\n                // Store the response in the cache (cloning because the browser will consume\n                // the body during the caching operation).\n                yield (yield this.cache).put(req, res.clone());\n                // Store the age of the cache.\n                const ageTable = yield this.ageTable;\n                yield ageTable.write(req.url, { age: this.adapter.time });\n                // Sync the LRU chain to non-volatile storage.\n                yield this.syncLru();\n            });\n        }\n        /**\n         * Delete all of the saved state which this group uses to track resources.\n         */\n        cleanup() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Remove both the cache and the database entries which track LRU stats.\n                yield Promise.all([\n                    this.scope.caches.delete(`${this.prefix}:dynamic:${this.config.name}:cache`),\n                    this.db.delete(`${this.prefix}:dynamic:${this.config.name}:age`),\n                    this.db.delete(`${this.prefix}:dynamic:${this.config.name}:lru`),\n                ]);\n            });\n        }\n        /**\n         * Clear the state of the cache for a particular resource.\n         *\n         * This doesn't remove the resource from the LRU table, that is assumed to have\n         * been done already. This clears the GET and HEAD versions of the request from\n         * the cache itself, as well as the metadata stored in the age table.\n         */\n        clearCacheForUrl(url) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                const [cache, ageTable] = yield Promise.all([this.cache, this.ageTable]);\n                yield Promise.all([\n                    cache.delete(this.adapter.newRequest(url, { method: 'GET' })),\n                    cache.delete(this.adapter.newRequest(url, { method: 'HEAD' })),\n                    ageTable.delete(url),\n                ]);\n            });\n        }\n        safeFetch(req) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                try {\n                    return this.scope.fetch(req);\n                }\n                catch (_a) {\n                    return this.adapter.newResponse(null, {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const BACKWARDS_COMPATIBILITY_NAVIGATION_URLS = [\n        { positive: true, regex: '^/.*$' },\n        { positive: false, regex: '^/.*\\\\.[^/]*$' },\n        { positive: false, regex: '^/.*__' },\n    ];\n    /**\n     * A specific version of the application, identified by a unique manifest\n     * as determined by its hash.\n     *\n     * Each `AppVersion` can be thought of as a published version of the app\n     * that can be installed as an update to any previously installed versions.\n     */\n    class AppVersion {\n        constructor(scope, adapter, database, idle, debugHandler, manifest, manifestHash) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.database = database;\n            this.idle = idle;\n            this.debugHandler = debugHandler;\n            this.manifest = manifest;\n            this.manifestHash = manifestHash;\n            /**\n             * A Map of absolute URL paths (/foo.txt) to the known hash of their\n             * contents (if available).\n             */\n            this.hashTable = new Map();\n            /**\n             * Tracks whether the manifest has encountered any inconsistencies.\n             */\n            this._okay = true;\n            // The hashTable within the manifest is an Object - convert it to a Map for easier lookups.\n            Object.keys(this.manifest.hashTable).forEach(url => {\n                this.hashTable.set(url, this.manifest.hashTable[url]);\n            });\n            // Process each `AssetGroup` declared in the manifest. Each declared group gets an `AssetGroup`\n            // instance\n            // created for it, of a type that depends on the configuration mode.\n            this.assetGroups = (manifest.assetGroups || []).map(config => {\n                // Every asset group has a cache that's prefixed by the manifest hash and the name of the\n                // group.\n                const prefix = `${adapter.cacheNamePrefix}:${this.manifestHash}:assets`;\n                // Check the caching mode, which determines when resources will be fetched/updated.\n                switch (config.installMode) {\n                    case 'prefetch':\n                        return new PrefetchAssetGroup(this.scope, this.adapter, this.idle, config, this.hashTable, this.database, prefix);\n                    case 'lazy':\n                        return new LazyAssetGroup(this.scope, this.adapter, this.idle, config, this.hashTable, this.database, prefix);\n                }\n            });\n            // Process each `DataGroup` declared in the manifest.\n            this.dataGroups =\n                (manifest.dataGroups || [])\n                    .map(config => new DataGroup(this.scope, this.adapter, config, this.database, this.debugHandler, `${adapter.cacheNamePrefix}:${config.version}:data`));\n            // This keeps backwards compatibility with app versions without navigation urls.\n            // Fix: https://github.com/angular/angular/issues/27209\n            manifest.navigationUrls = manifest.navigationUrls || BACKWARDS_COMPATIBILITY_NAVIGATION_URLS;\n            // Create `include`/`exclude` RegExps for the `navigationUrls` declared in the manifest.\n            const includeUrls = manifest.navigationUrls.filter(spec => spec.positive);\n            const excludeUrls = manifest.navigationUrls.filter(spec => !spec.positive);\n            this.navigationUrls = {\n                include: includeUrls.map(spec => new RegExp(spec.regex)),\n                exclude: excludeUrls.map(spec => new RegExp(spec.regex)),\n            };\n        }\n        get okay() { return this._okay; }\n        /**\n         * Fully initialize this version of the application. If this Promise resolves successfully, all\n         * required\n         * data has been safely downloaded.\n         */\n        initializeFully(updateFrom) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                try {\n                    // Fully initialize each asset group, in series. Starts with an empty Promise,\n                    // and waits for the previous groups to have been initialized before initializing\n                    // the next one in turn.\n                    yield this.assetGroups.reduce((previous, group) => __awaiter$2(this, void 0, void 0, function* () {\n                        // Wait for the previous groups to complete initialization. If there is a\n                        // failure, this will throw, and each subsequent group will throw, until the\n                        // whole sequence fails.\n                        yield previous;\n                        // Initialize this group.\n                        return group.initializeFully(updateFrom);\n                    }), Promise.resolve());\n                }\n                catch (err) {\n                    this._okay = false;\n                    throw err;\n                }\n            });\n        }\n        handleFetch(req, context) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                // Check the request against each `AssetGroup` in sequence. If an `AssetGroup` can't handle the\n                // request,\n                // it will return `null`. Thus, the first non-null response is the SW's answer to the request.\n                // So reduce\n                // the group list, keeping track of a possible response. If there is one, it gets passed\n                // through, and if\n                // not the next group is consulted to produce a candidate response.\n                const asset = yield this.assetGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    // Wait on the previous potential response. If it's not null, it should just be passed\n                    // through.\n                    const resp = yield potentialResponse;\n                    if (resp !== null) {\n                        return resp;\n                    }\n                    // No response has been found yet. Maybe this group will have one.\n                    return group.handleFetch(req, context);\n                }), Promise.resolve(null));\n                // The result of the above is the asset response, if there is any, or null otherwise. Return the\n                // asset\n                // response if there was one. If not, check with the data caching groups.\n                if (asset !== null) {\n                    return asset;\n                }\n                // Perform the same reduction operation as above, but this time processing\n                // the data caching groups.\n                const data = yield this.dataGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    const resp = yield potentialResponse;\n                    if (resp !== null) {\n                        return resp;\n                    }\n                    return group.handleFetch(req, context);\n                }), Promise.resolve(null));\n                // If the data caching group returned a response, go with it.\n                if (data !== null) {\n                    return data;\n                }\n                // Next, check if this is a navigation request for a route. Detect circular\n                // navigations by checking if the request URL is the same as the index URL.\n                if (req.url !== this.manifest.index && this.isNavigationRequest(req)) {\n                    // This was a navigation request. Re-enter `handleFetch` with a request for\n                    // the URL.\n                    return this.handleFetch(this.adapter.newRequest(this.manifest.index), context);\n                }\n                return null;\n            });\n        }\n        /**\n         * Determine whether the request is a navigation request.\n         * Takes into account: Request mode, `Accept` header, `navigationUrls` patterns.\n         */\n        isNavigationRequest(req) {\n            if (req.mode !== 'navigate') {\n                return false;\n            }\n            if (!this.acceptsTextHtml(req)) {\n                return false;\n            }\n            const urlPrefix = this.scope.registration.scope.replace(/\\/$/, '');\n            const url = req.url.startsWith(urlPrefix) ? req.url.substr(urlPrefix.length) : req.url;\n            const urlWithoutQueryOrHash = url.replace(/[?#].*$/, '');\n            return this.navigationUrls.include.some(regex => regex.test(urlWithoutQueryOrHash)) &&\n                !this.navigationUrls.exclude.some(regex => regex.test(urlWithoutQueryOrHash));\n        }\n        /**\n         * Check this version for a given resource with a particular hash.\n         */\n        lookupResourceWithHash(url, hash) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                // Verify that this version has the requested resource cached. If not,\n                // there's no point in trying.\n                if (!this.hashTable.has(url)) {\n                    return null;\n                }\n                // Next, check whether the resource has the correct hash. If not, any cached\n                // response isn't usable.\n                if (this.hashTable.get(url) !== hash) {\n                    return null;\n                }\n                const cacheState = yield this.lookupResourceWithoutHash(url);\n                return cacheState && cacheState.response;\n            });\n        }\n        /**\n         * Check this version for a given resource regardless of its hash.\n         */\n        lookupResourceWithoutHash(url) {\n            // Limit the search to asset groups, and only scan the cache, don't\n            // load resources from the network.\n            return this.assetGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                const resp = yield potentialResponse;\n                if (resp !== null) {\n                    return resp;\n                }\n                // fetchFromCacheOnly() avoids any network fetches, and returns the\n                // full set of cache data, not just the Response.\n                return group.fetchFromCacheOnly(url);\n            }), Promise.resolve(null));\n        }\n        /**\n         * List all unhashed resources from all asset groups.\n         */\n        previouslyCachedResources() {\n            return this.assetGroups.reduce((resources, group) => __awaiter$2(this, void 0, void 0, function* () {\n                return (yield resources).concat(yield group.unhashedResources());\n            }), Promise.resolve([]));\n        }\n        recentCacheStatus(url) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                return this.assetGroups.reduce((current, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    const status = yield current;\n                    if (status === UpdateCacheStatus.CACHED) {\n                        return status;\n                    }\n                    const groupStatus = yield group.cacheStatus(url);\n                    if (groupStatus === UpdateCacheStatus.NOT_CACHED) {\n                        return status;\n                    }\n                    return groupStatus;\n                }), Promise.resolve(UpdateCacheStatus.NOT_CACHED));\n            });\n        }\n        /**\n         * Erase this application version, by cleaning up all the caches.\n         */\n        cleanup() {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                yield Promise.all(this.assetGroups.map(group => group.cleanup()));\n                yield Promise.all(this.dataGroups.map(group => group.cleanup()));\n            });\n        }\n        /**\n         * Get the opaque application data which was provided with the manifest.\n         */\n        get appData() { return this.manifest.appData || null; }\n        /**\n         * Check whether a request accepts `text/html` (based on the `Accept` header).\n         */\n        acceptsTextHtml(req) {\n            const accept = req.headers.get('Accept');\n            if (accept === null) {\n                return false;\n            }\n            const values = accept.split(',');\n            return values.some(value => value.trim().toLowerCase() === 'text/html');\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$3 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const DEBUG_LOG_BUFFER_SIZE = 100;\n    class DebugHandler {\n        constructor(driver, adapter) {\n            this.driver = driver;\n            this.adapter = adapter;\n            // There are two debug log message arrays. debugLogA records new debugging messages.\n            // Once it reaches DEBUG_LOG_BUFFER_SIZE, the array is moved to debugLogB and a new\n            // array is assigned to debugLogA. This ensures that insertion to the debug log is\n            // always O(1) no matter the number of logged messages, and that the total number\n            // of messages in the log never exceeds 2 * DEBUG_LOG_BUFFER_SIZE.\n            this.debugLogA = [];\n            this.debugLogB = [];\n        }\n        handleFetch(req) {\n            return __awaiter$3(this, void 0, void 0, function* () {\n                const [state, versions, idle] = yield Promise.all([\n                    this.driver.debugState(),\n                    this.driver.debugVersions(),\n                    this.driver.debugIdleState(),\n                ]);\n                const msgState = `NGSW Debug Info:\n\nDriver state: ${state.state} (${state.why})\nLatest manifest hash: ${state.latestHash || 'none'}\nLast update check: ${this.since(state.lastUpdateCheck)}`;\n                const msgVersions = versions\n                    .map(version => `=== Version ${version.hash} ===\n\nClients: ${version.clients.join(', ')}`)\n                    .join('\\n\\n');\n                const msgIdle = `=== Idle Task Queue ===\nLast update tick: ${this.since(idle.lastTrigger)}\nLast update run: ${this.since(idle.lastRun)}\nTask queue:\n${idle.queue.map(v => ' * ' + v).join('\\n')}\n\nDebug log:\n${this.formatDebugLog(this.debugLogB)}\n${this.formatDebugLog(this.debugLogA)}\n`;\n                return this.adapter.newResponse(`${msgState}\n\n${msgVersions}\n\n${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }) });\n            });\n        }\n        since(time) {\n            if (time === null) {\n                return 'never';\n            }\n            let age = this.adapter.time - time;\n            const days = Math.floor(age / 86400000);\n            age = age % 86400000;\n            const hours = Math.floor(age / 3600000);\n            age = age % 3600000;\n            const minutes = Math.floor(age / 60000);\n            age = age % 60000;\n            const seconds = Math.floor(age / 1000);\n            const millis = age % 1000;\n            return '' + (days > 0 ? `${days}d` : '') + (hours > 0 ? `${hours}h` : '') +\n                (minutes > 0 ? `${minutes}m` : '') + (seconds > 0 ? `${seconds}s` : '') +\n                (millis > 0 ? `${millis}u` : '');\n        }\n        log(value, context = '') {\n            // Rotate the buffers if debugLogA has grown too large.\n            if (this.debugLogA.length === DEBUG_LOG_BUFFER_SIZE) {\n                this.debugLogB = this.debugLogA;\n                this.debugLogA = [];\n            }\n            // Convert errors to string for logging.\n            if (typeof value !== 'string') {\n                value = this.errorToString(value);\n            }\n            // Log the message.\n            this.debugLogA.push({ value, time: this.adapter.time, context });\n        }\n        errorToString(err) { return `${err.name}(${err.message}, ${err.stack})`; }\n        formatDebugLog(log) {\n            return log.map(entry => `[${this.since(entry.time)}] ${entry.value} ${entry.context}`)\n                .join('\\n');\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$4 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    class IdleScheduler {\n        constructor(adapter, threshold, debug) {\n            this.adapter = adapter;\n            this.threshold = threshold;\n            this.debug = debug;\n            this.queue = [];\n            this.scheduled = null;\n            this.empty = Promise.resolve();\n            this.emptyResolve = null;\n            this.lastTrigger = null;\n            this.lastRun = null;\n        }\n        trigger() {\n            return __awaiter$4(this, void 0, void 0, function* () {\n                this.lastTrigger = this.adapter.time;\n                if (this.queue.length === 0) {\n                    return;\n                }\n                if (this.scheduled !== null) {\n                    this.scheduled.cancel = true;\n                }\n                const scheduled = {\n                    cancel: false,\n                };\n                this.scheduled = scheduled;\n                yield this.adapter.timeout(this.threshold);\n                if (scheduled.cancel) {\n                    return;\n                }\n                this.scheduled = null;\n                yield this.execute();\n            });\n        }\n        execute() {\n            return __awaiter$4(this, void 0, void 0, function* () {\n                this.lastRun = this.adapter.time;\n                while (this.queue.length > 0) {\n                    const queue = this.queue;\n                    this.queue = [];\n                    yield queue.reduce((previous, task) => __awaiter$4(this, void 0, void 0, function* () {\n                        yield previous;\n                        try {\n                            yield task.run();\n                        }\n                        catch (err) {\n                            this.debug.log(err, `while running idle task ${task.desc}`);\n                        }\n                    }), Promise.resolve());\n                }\n                if (this.emptyResolve !== null) {\n                    this.emptyResolve();\n                    this.emptyResolve = null;\n                }\n                this.empty = Promise.resolve();\n            });\n        }\n        schedule(desc, run) {\n            this.queue.push({ desc, run });\n            if (this.emptyResolve === null) {\n                this.empty = new Promise(resolve => { this.emptyResolve = resolve; });\n            }\n        }\n        get size() { return this.queue.length; }\n        get taskDescriptions() { return this.queue.map(task => task.desc); }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    function hashManifest(manifest) {\n        return sha1(JSON.stringify(manifest));\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    function isMsgCheckForUpdates(msg) {\n        return msg.action === 'CHECK_FOR_UPDATES';\n    }\n    function isMsgActivateUpdate(msg) {\n        return msg.action === 'ACTIVATE_UPDATE';\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$5 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const IDLE_THRESHOLD = 5000;\n    const SUPPORTED_CONFIG_VERSION = 1;\n    const NOTIFICATION_OPTION_NAMES = [\n        'actions', 'badge', 'body', 'data', 'dir', 'icon', 'image', 'lang', 'renotify',\n        'requireInteraction', 'silent', 'tag', 'timestamp', 'title', 'vibrate'\n    ];\n    var DriverReadyState;\n    (function (DriverReadyState) {\n        // The SW is operating in a normal mode, responding to all traffic.\n        DriverReadyState[DriverReadyState[\"NORMAL\"] = 0] = \"NORMAL\";\n        // The SW does not have a clean installation of the latest version of the app, but older\n        // cached versions are safe to use so long as they don't try to fetch new dependencies.\n        // This is a degraded state.\n        DriverReadyState[DriverReadyState[\"EXISTING_CLIENTS_ONLY\"] = 1] = \"EXISTING_CLIENTS_ONLY\";\n        // The SW has decided that caching is completely unreliable, and is forgoing request\n        // handling until the next restart.\n        DriverReadyState[DriverReadyState[\"SAFE_MODE\"] = 2] = \"SAFE_MODE\";\n    })(DriverReadyState || (DriverReadyState = {}));\n    class Driver {\n        constructor(scope, adapter, db) {\n            // Set up all the event handlers that the SW needs.\n            this.scope = scope;\n            this.adapter = adapter;\n            this.db = db;\n            /**\n             * Tracks the current readiness condition under which the SW is operating. This controls\n             * whether the SW attempts to respond to some or all requests.\n             */\n            this.state = DriverReadyState.NORMAL;\n            this.stateMessage = '(nominal)';\n            /**\n             * Tracks whether the SW is in an initialized state or not. Before initialization,\n             * it's not legal to respond to requests.\n             */\n            this.initialized = null;\n            /**\n             * Maps client IDs to the manifest hash of the application version being used to serve\n             * them. If a client ID is not present here, it has not yet been assigned a version.\n             *\n             * If a ManifestHash appears here, it is also present in the `versions` map below.\n             */\n            this.clientVersionMap = new Map();\n            /**\n             * Maps manifest hashes to instances of `AppVersion` for those manifests.\n             */\n            this.versions = new Map();\n            /**\n             * The latest version fetched from the server.\n             *\n             * Valid after initialization has completed.\n             */\n            this.latestHash = null;\n            this.lastUpdateCheck = null;\n            /**\n             * Whether there is a check for updates currently scheduled due to navigation.\n             */\n            this.scheduledNavUpdateCheck = false;\n            /**\n             * Keep track of whether we have logged an invalid `only-if-cached` request.\n             * (See `.onFetch()` for details.)\n             */\n            this.loggedInvalidOnlyIfCachedRequest = false;\n            // The install event is triggered when the service worker is first installed.\n            this.scope.addEventListener('install', (event) => {\n                // SW code updates are separate from application updates, so code updates are\n                // almost as straightforward as restarting the SW. Because of this, it's always\n                // safe to skip waiting until application tabs are closed, and activate the new\n                // SW version immediately.\n                event.waitUntil(this.scope.skipWaiting());\n            });\n            // The activate event is triggered when this version of the service worker is\n            // first activated.\n            this.scope.addEventListener('activate', (event) => {\n                event.waitUntil((() => __awaiter$5(this, void 0, void 0, function* () {\n                    // As above, it's safe to take over from existing clients immediately, since the new SW\n                    // version will continue to serve the old application.\n                    yield this.scope.clients.claim();\n                    // Once all clients have been taken over, we can delete caches used by old versions of\n                    // `@angular/service-worker`, which are no longer needed. This can happen in the background.\n                    this.idle.schedule('activate: cleanup-old-sw-caches', () => __awaiter$5(this, void 0, void 0, function* () {\n                        try {\n                            yield this.cleanupOldSwCaches();\n                        }\n                        catch (err) {\n                            // Nothing to do - cleanup failed. Just log it.\n                            this.debugger.log(err, 'cleanupOldSwCaches @ activate: cleanup-old-sw-caches');\n                        }\n                    }));\n                }))());\n                // Rather than wait for the first fetch event, which may not arrive until\n                // the next time the application is loaded, the SW takes advantage of the\n                // activation event to schedule initialization. However, if this were run\n                // in the context of the 'activate' event, waitUntil() here would cause fetch\n                // events to block until initialization completed. Thus, the SW does a\n                // postMessage() to itself, to schedule a new event loop iteration with an\n                // entirely separate event context. The SW will be kept alive by waitUntil()\n                // within that separate context while initialization proceeds, while at the\n                // same time the activation event is allowed to resolve and traffic starts\n                // being served.\n                if (this.scope.registration.active !== null) {\n                    this.scope.registration.active.postMessage({ action: 'INITIALIZE' });\n                }\n            });\n            // Handle the fetch, message, and push events.\n            this.scope.addEventListener('fetch', (event) => this.onFetch(event));\n            this.scope.addEventListener('message', (event) => this.onMessage(event));\n            this.scope.addEventListener('push', (event) => this.onPush(event));\n            this.scope.addEventListener('notificationclick', (event) => this.onClick(event));\n            // The debugger generates debug pages in response to debugging requests.\n            this.debugger = new DebugHandler(this, this.adapter);\n            // The IdleScheduler will execute idle tasks after a given delay.\n            this.idle = new IdleScheduler(this.adapter, IDLE_THRESHOLD, this.debugger);\n        }\n        /**\n         * The handler for fetch events.\n         *\n         * This is the transition point between the synchronous event handler and the\n         * asynchronous execution that eventually resolves for respondWith() and waitUntil().\n         */\n        onFetch(event) {\n            const req = event.request;\n            const scopeUrl = this.scope.registration.scope;\n            const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);\n            if (req.headers.has('ngsw-bypass') || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) {\n                return;\n            }\n            // The only thing that is served unconditionally is the debug page.\n            if (requestUrlObj.path === '/ngsw/state') {\n                // Allow the debugger to handle the request, but don't affect SW state in any other way.\n                event.respondWith(this.debugger.handleFetch(req));\n                return;\n            }\n            // If the SW is in a broken state where it's not safe to handle requests at all,\n            // returning causes the request to fall back on the network. This is preferred over\n            // `respondWith(fetch(req))` because the latter still shows in DevTools that the\n            // request was handled by the SW.\n            if (this.state === DriverReadyState.SAFE_MODE) {\n                // Even though the worker is in safe mode, idle tasks still need to happen so\n                // things like update checks, etc. can take place.\n                event.waitUntil(this.idle.trigger());\n                return;\n            }\n            // Although \"passive mixed content\" (like images) only produces a warning without a\n            // ServiceWorker, fetching it via a ServiceWorker results in an error. Let such requests be\n            // handled by the browser, since handling with the ServiceWorker would fail anyway.\n            // See https://github.com/angular/angular/issues/23012#issuecomment-376430187 for more details.\n            if (requestUrlObj.origin.startsWith('http:') && scopeUrl.startsWith('https:')) {\n                // Still, log the incident for debugging purposes.\n                this.debugger.log(`Ignoring passive mixed content request: Driver.fetch(${req.url})`);\n                return;\n            }\n            // When opening DevTools in Chrome, a request is made for the current URL (and possibly related\n            // resources, e.g. scripts) with `cache: 'only-if-cached'` and `mode: 'no-cors'`. These request\n            // will eventually fail, because `only-if-cached` is only allowed to be used with\n            // `mode: 'same-origin'`.\n            // This is likely a bug in Chrome DevTools. Avoid handling such requests.\n            // (See also https://github.com/angular/angular/issues/22362.)\n            // TODO(gkalpak): Remove once no longer necessary (i.e. fixed in Chrome DevTools).\n            if (req.cache === 'only-if-cached' && req.mode !== 'same-origin') {\n                // Log the incident only the first time it happens, to avoid spamming the logs.\n                if (!this.loggedInvalidOnlyIfCachedRequest) {\n                    this.loggedInvalidOnlyIfCachedRequest = true;\n                    this.debugger.log(`Ignoring invalid request: 'only-if-cached' can be set only with 'same-origin' mode`, `Driver.fetch(${req.url}, cache: ${req.cache}, mode: ${req.mode})`);\n                }\n                return;\n            }\n            // Past this point, the SW commits to handling the request itself. This could still\n            // fail (and result in `state` being set to `SAFE_MODE`), but even in that case the\n            // SW will still deliver a response.\n            event.respondWith(this.handleFetch(event));\n        }\n        /**\n         * The handler for message events.\n         */\n        onMessage(event) {\n            // Ignore message events when the SW is in safe mode, for now.\n            if (this.state === DriverReadyState.SAFE_MODE) {\n                return;\n            }\n            // If the message doesn't have the expected signature, ignore it.\n            const data = event.data;\n            if (!data || !data.action) {\n                return;\n            }\n            event.waitUntil((() => __awaiter$5(this, void 0, void 0, function* () {\n                // Initialization is the only event which is sent directly from the SW to itself, and thus\n                // `event.source` is not a `Client`. Handle it here, before the check for `Client` sources.\n                if (data.action === 'INITIALIZE') {\n                    return this.ensureInitialized(event);\n                }\n                // Only messages from true clients are accepted past this point.\n                // This is essentially a typecast.\n                if (!this.adapter.isClient(event.source)) {\n                    return;\n                }\n                // Handle the message and keep the SW alive until it's handled.\n                yield this.ensureInitialized(event);\n                yield this.handleMessage(data, event.source);\n            }))());\n        }\n        onPush(msg) {\n            // Push notifications without data have no effect.\n            if (!msg.data) {\n                return;\n            }\n            // Handle the push and keep the SW alive until it's handled.\n            msg.waitUntil(this.handlePush(msg.data.json()));\n        }\n        onClick(event) {\n            // Handle the click event and keep the SW alive until it's handled.\n            event.waitUntil(this.handleClick(event.notification, event.action));\n        }\n        ensureInitialized(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Since the SW may have just been started, it may or may not have been initialized already.\n                // `this.initialized` will be `null` if initialization has not yet been attempted, or will be a\n                // `Promise` which will resolve (successfully or unsuccessfully) if it has.\n                if (this.initialized !== null) {\n                    return this.initialized;\n                }\n                // Initialization has not yet been attempted, so attempt it. This should only ever happen once\n                // per SW instantiation.\n                try {\n                    this.initialized = this.initialize();\n                    yield this.initialized;\n                }\n                catch (error) {\n                    // If initialization fails, the SW needs to enter a safe state, where it declines to respond\n                    // to network requests.\n                    this.state = DriverReadyState.SAFE_MODE;\n                    this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`;\n                    throw error;\n                }\n                finally {\n                    // Regardless if initialization succeeded, background tasks still need to happen.\n                    event.waitUntil(this.idle.trigger());\n                }\n            });\n        }\n        handleMessage(msg, from) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                if (isMsgCheckForUpdates(msg)) {\n                    const action = (() => __awaiter$5(this, void 0, void 0, function* () { yield this.checkForUpdate(); }))();\n                    yield this.reportStatus(from, action, msg.statusNonce);\n                }\n                else if (isMsgActivateUpdate(msg)) {\n                    yield this.reportStatus(from, this.updateClient(from), msg.statusNonce);\n                }\n            });\n        }\n        handlePush(data) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.broadcast({\n                    type: 'PUSH',\n                    data,\n                });\n                if (!data.notification || !data.notification.title) {\n                    return;\n                }\n                const desc = data.notification;\n                let options = {};\n                NOTIFICATION_OPTION_NAMES.filter(name => desc.hasOwnProperty(name))\n                    .forEach(name => options[name] = desc[name]);\n                yield this.scope.registration.showNotification(desc['title'], options);\n            });\n        }\n        handleClick(notification, action) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                notification.close();\n                const options = {};\n                // The filter uses `name in notification` because the properties are on the prototype so\n                // hasOwnProperty does not work here\n                NOTIFICATION_OPTION_NAMES.filter(name => name in notification)\n                    .forEach(name => options[name] = notification[name]);\n                yield this.broadcast({\n                    type: 'NOTIFICATION_CLICK',\n                    data: { action, notification: options },\n                });\n            });\n        }\n        reportStatus(client, promise, nonce) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const response = { type: 'STATUS', nonce, status: true };\n                try {\n                    yield promise;\n                    client.postMessage(response);\n                }\n                catch (e) {\n                    client.postMessage(Object.assign(Object.assign({}, response), { status: false, error: e.toString() }));\n                }\n            });\n        }\n        updateClient(client) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Figure out which version the client is on. If it's not on the latest,\n                // it needs to be moved.\n                const existing = this.clientVersionMap.get(client.id);\n                if (existing === this.latestHash) {\n                    // Nothing to do, this client is already on the latest version.\n                    return;\n                }\n                // Switch the client over.\n                let previous = undefined;\n                // Look up the application data associated with the existing version. If there\n                // isn't any, fall back on using the hash.\n                if (existing !== undefined) {\n                    const existingVersion = this.versions.get(existing);\n                    previous = this.mergeHashWithAppData(existingVersion.manifest, existing);\n                }\n                // Set the current version used by the client, and sync the mapping to disk.\n                this.clientVersionMap.set(client.id, this.latestHash);\n                yield this.sync();\n                // Notify the client about this activation.\n                const current = this.versions.get(this.latestHash);\n                const notice = {\n                    type: 'UPDATE_ACTIVATED',\n                    previous,\n                    current: this.mergeHashWithAppData(current.manifest, this.latestHash),\n                };\n                client.postMessage(notice);\n            });\n        }\n        handleFetch(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                try {\n                    // Ensure the SW instance has been initialized.\n                    yield this.ensureInitialized(event);\n                }\n                catch (_a) {\n                    // Since the SW is already committed to responding to the currently active request,\n                    // respond with a network fetch.\n                    return this.safeFetch(event.request);\n                }\n                // On navigation requests, check for new updates.\n                if (event.request.mode === 'navigate' && !this.scheduledNavUpdateCheck) {\n                    this.scheduledNavUpdateCheck = true;\n                    this.idle.schedule('check-updates-on-navigation', () => __awaiter$5(this, void 0, void 0, function* () {\n                        this.scheduledNavUpdateCheck = false;\n                        yield this.checkForUpdate();\n                    }));\n                }\n                // Decide which version of the app to use to serve this request. This is asynchronous as in\n                // some cases, a record will need to be written to disk about the assignment that is made.\n                const appVersion = yield this.assignVersion(event);\n                // Bail out\n                if (appVersion === null) {\n                    event.waitUntil(this.idle.trigger());\n                    return this.safeFetch(event.request);\n                }\n                let res = null;\n                try {\n                    // Handle the request. First try the AppVersion. If that doesn't work, fall back on the\n                    // network.\n                    res = yield appVersion.handleFetch(event.request, event);\n                }\n                catch (err) {\n                    if (err.isCritical) {\n                        // Something went wrong with the activation of this version.\n                        yield this.versionFailed(appVersion, err);\n                        event.waitUntil(this.idle.trigger());\n                        return this.safeFetch(event.request);\n                    }\n                    throw err;\n                }\n                // The AppVersion will only return null if the manifest doesn't specify what to do about this\n                // request. In that case, just fall back on the network.\n                if (res === null) {\n                    event.waitUntil(this.idle.trigger());\n                    return this.safeFetch(event.request);\n                }\n                // Trigger the idle scheduling system. The Promise returned by trigger() will resolve after\n                // a specific amount of time has passed. If trigger() hasn't been called again by then (e.g.\n                // on a subsequent request), the idle task queue will be drained and the Promise won't resolve\n                // until that operation is complete as well.\n                event.waitUntil(this.idle.trigger());\n                // The AppVersion returned a usable response, so return it.\n                return res;\n            });\n        }\n        /**\n         * Attempt to quickly reach a state where it's safe to serve responses.\n         */\n        initialize() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // On initialization, all of the serialized state is read out of the 'control'\n                // table. This includes:\n                // - map of hashes to manifests of currently loaded application versions\n                // - map of client IDs to their pinned versions\n                // - record of the most recently fetched manifest hash\n                //\n                // If these values don't exist in the DB, then this is the either the first time\n                // the SW has run or the DB state has been wiped or is inconsistent. In that case,\n                // load a fresh copy of the manifest and reset the state from scratch.\n                // Open up the DB table.\n                const table = yield this.db.open('control');\n                // Attempt to load the needed state from the DB. If this fails, the catch {} block\n                // will populate these variables with freshly constructed values.\n                let manifests, assignments, latest;\n                try {\n                    // Read them from the DB simultaneously.\n                    [manifests, assignments, latest] = yield Promise.all([\n                        table.read('manifests'),\n                        table.read('assignments'),\n                        table.read('latest'),\n                    ]);\n                    // Successfully loaded from saved state. This implies a manifest exists, so\n                    // the update check needs to happen in the background.\n                    this.idle.schedule('init post-load (update, cleanup)', () => __awaiter$5(this, void 0, void 0, function* () {\n                        yield this.checkForUpdate();\n                        try {\n                            yield this.cleanupCaches();\n                        }\n                        catch (err) {\n                            // Nothing to do - cleanup failed. Just log it.\n                            this.debugger.log(err, 'cleanupCaches @ init post-load');\n                        }\n                    }));\n                }\n                catch (_) {\n                    // Something went wrong. Try to start over by fetching a new manifest from the\n                    // server and building up an empty initial state.\n                    const manifest = yield this.fetchLatestManifest();\n                    const hash = hashManifest(manifest);\n                    manifests = {};\n                    manifests[hash] = manifest;\n                    assignments = {};\n                    latest = { latest: hash };\n                    // Save the initial state to the DB.\n                    yield Promise.all([\n                        table.write('manifests', manifests),\n                        table.write('assignments', assignments),\n                        table.write('latest', latest),\n                    ]);\n                }\n                // At this point, either the state has been loaded successfully, or fresh state\n                // with a new copy of the manifest has been produced. At this point, the `Driver`\n                // can have its internals hydrated from the state.\n                // Initialize the `versions` map by setting each hash to a new `AppVersion` instance\n                // for that manifest.\n                Object.keys(manifests).forEach((hash) => {\n                    const manifest = manifests[hash];\n                    // If the manifest is newly initialized, an AppVersion may have already been\n                    // created for it.\n                    if (!this.versions.has(hash)) {\n                        this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash));\n                    }\n                });\n                // Map each client ID to its associated hash. Along the way, verify that the hash\n                // is still valid for that client ID. It should not be possible for a client to\n                // still be associated with a hash that was since removed from the state.\n                Object.keys(assignments).forEach((clientId) => {\n                    const hash = assignments[clientId];\n                    if (this.versions.has(hash)) {\n                        this.clientVersionMap.set(clientId, hash);\n                    }\n                    else {\n                        this.clientVersionMap.set(clientId, latest.latest);\n                        this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`);\n                    }\n                });\n                // Set the latest version.\n                this.latestHash = latest.latest;\n                // Finally, assert that the latest version is in fact loaded.\n                if (!this.versions.has(latest.latest)) {\n                    throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`);\n                }\n                // Finally, wait for the scheduling of initialization of all versions in the\n                // manifest. Ordinarily this just schedules the initializations to happen during\n                // the next idle period, but in development mode this might actually wait for the\n                // full initialization.\n                // If any of these initializations fail, versionFailed() will be called either\n                // synchronously or asynchronously to handle the failure and re-map clients.\n                yield Promise.all(Object.keys(manifests).map((hash) => __awaiter$5(this, void 0, void 0, function* () {\n                    try {\n                        // Attempt to schedule or initialize this version. If this operation is\n                        // successful, then initialization either succeeded or was scheduled. If\n                        // it fails, then full initialization was attempted and failed.\n                        yield this.scheduleInitialization(this.versions.get(hash));\n                    }\n                    catch (err) {\n                        this.debugger.log(err, `initialize: schedule init of ${hash}`);\n                        return false;\n                    }\n                })));\n            });\n        }\n        lookupVersionByHash(hash, debugName = 'lookupVersionByHash') {\n            // The version should exist, but check just in case.\n            if (!this.versions.has(hash)) {\n                throw new Error(`Invariant violated (${debugName}): want AppVersion for ${hash} but not loaded`);\n            }\n            return this.versions.get(hash);\n        }\n        /**\n         * Decide which version of the manifest to use for the event.\n         */\n        assignVersion(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // First, check whether the event has a (non empty) client ID. If it does, the version may\n                // already be associated.\n                const clientId = event.clientId;\n                if (clientId) {\n                    // Check if there is an assigned client id.\n                    if (this.clientVersionMap.has(clientId)) {\n                        // There is an assignment for this client already.\n                        const hash = this.clientVersionMap.get(clientId);\n                        let appVersion = this.lookupVersionByHash(hash, 'assignVersion');\n                        // Ordinarily, this client would be served from its assigned version. But, if this\n                        // request is a navigation request, this client can be updated to the latest\n                        // version immediately.\n                        if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash &&\n                            appVersion.isNavigationRequest(event.request)) {\n                            // Update this client to the latest version immediately.\n                            if (this.latestHash === null) {\n                                throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                            }\n                            const client = yield this.scope.clients.get(clientId);\n                            yield this.updateClient(client);\n                            appVersion = this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                        }\n                        // TODO: make sure the version is valid.\n                        return appVersion;\n                    }\n                    else {\n                        // This is the first time this client ID has been seen. Whether the SW is in a\n                        // state to handle new clients depends on the current readiness state, so check\n                        // that first.\n                        if (this.state !== DriverReadyState.NORMAL) {\n                            // It's not safe to serve new clients in the current state. It's possible that\n                            // this is an existing client which has not been mapped yet (see below) but\n                            // even if that is the case, it's invalid to make an assignment to a known\n                            // invalid version, even if that assignment was previously implicit. Return\n                            // undefined here to let the caller know that no assignment is possible at\n                            // this time.\n                            return null;\n                        }\n                        // It's safe to handle this request. Two cases apply. Either:\n                        // 1) the browser assigned a client ID at the time of the navigation request, and\n                        //    this is truly the first time seeing this client, or\n                        // 2) a navigation request came previously from the same client, but with no client\n                        //    ID attached. Browsers do this to avoid creating a client under the origin in\n                        //    the event the navigation request is just redirected.\n                        //\n                        // In case 1, the latest version can safely be used.\n                        // In case 2, the latest version can be used, with the assumption that the previous\n                        // navigation request was answered under the same version. This assumption relies\n                        // on the fact that it's unlikely an update will come in between the navigation\n                        // request and requests for subsequent resources on that page.\n                        // First validate the current state.\n                        if (this.latestHash === null) {\n                            throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                        }\n                        // Pin this client ID to the current latest version, indefinitely.\n                        this.clientVersionMap.set(clientId, this.latestHash);\n                        yield this.sync();\n                        // Return the latest `AppVersion`.\n                        return this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                    }\n                }\n                else {\n                    // No client ID was associated with the request. This must be a navigation request\n                    // for a new client. First check that the SW is accepting new clients.\n                    if (this.state !== DriverReadyState.NORMAL) {\n                        return null;\n                    }\n                    // Serve it with the latest version, and assume that the client will actually get\n                    // associated with that version on the next request.\n                    // First validate the current state.\n                    if (this.latestHash === null) {\n                        throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                    }\n                    // Return the latest `AppVersion`.\n                    return this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                }\n            });\n        }\n        fetchLatestManifest(ignoreOfflineError = false) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const res = yield this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));\n                if (!res.ok) {\n                    if (res.status === 404) {\n                        yield this.deleteAllCaches();\n                        yield this.scope.registration.unregister();\n                    }\n                    else if (res.status === 504 && ignoreOfflineError) {\n                        return null;\n                    }\n                    throw new Error(`Manifest fetch failed! (status: ${res.status})`);\n                }\n                this.lastUpdateCheck = this.adapter.time;\n                return res.json();\n            });\n        }\n        deleteAllCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield (yield this.scope.caches.keys())\n                    .filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:`))\n                    .reduce((previous, key) => __awaiter$5(this, void 0, void 0, function* () {\n                    yield Promise.all([\n                        previous,\n                        this.scope.caches.delete(key),\n                    ]);\n                }), Promise.resolve());\n            });\n        }\n        /**\n         * Schedule the SW's attempt to reach a fully prefetched state for the given AppVersion\n         * when the SW is not busy and has connectivity. This returns a Promise which must be\n         * awaited, as under some conditions the AppVersion might be initialized immediately.\n         */\n        scheduleInitialization(appVersion) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const initialize = () => __awaiter$5(this, void 0, void 0, function* () {\n                    try {\n                        yield appVersion.initializeFully();\n                    }\n                    catch (err) {\n                        this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`);\n                        yield this.versionFailed(appVersion, err);\n                    }\n                });\n                // TODO: better logic for detecting localhost.\n                if (this.scope.registration.scope.indexOf('://localhost') > -1) {\n                    return initialize();\n                }\n                this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize);\n            });\n        }\n        versionFailed(appVersion, err) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // This particular AppVersion is broken. First, find the manifest hash.\n                const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);\n                if (broken === undefined) {\n                    // This version is no longer in use anyway, so nobody cares.\n                    return;\n                }\n                const brokenHash = broken[0];\n                const affectedClients = Array.from(this.clientVersionMap.entries())\n                    .filter(([clientId, hash]) => hash === brokenHash)\n                    .map(([clientId]) => clientId);\n                // TODO: notify affected apps.\n                // The action taken depends on whether the broken manifest is the active (latest) or not.\n                // If so, the SW cannot accept new clients, but can continue to service old ones.\n                if (this.latestHash === brokenHash) {\n                    // The latest manifest is broken. This means that new clients are at the mercy of the\n                    // network, but caches continue to be valid for previous versions. This is\n                    // unfortunate but unavoidable.\n                    this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;\n                    this.stateMessage = `Degraded due to: ${errorToString(err)}`;\n                    // Cancel the binding for the affected clients.\n                    affectedClients.forEach(clientId => this.clientVersionMap.delete(clientId));\n                }\n                else {\n                    // The latest version is viable, but this older version isn't. The only\n                    // possible remedy is to stop serving the older version and go to the network.\n                    // Put the affected clients on the latest version.\n                    affectedClients.forEach(clientId => this.clientVersionMap.set(clientId, this.latestHash));\n                }\n                try {\n                    yield this.sync();\n                }\n                catch (err2) {\n                    // We are already in a bad state. No need to make things worse.\n                    // Just log the error and move on.\n                    this.debugger.log(err2, `Driver.versionFailed(${err.message || err})`);\n                }\n            });\n        }\n        setupUpdate(manifest, hash) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash);\n                // Firstly, check if the manifest version is correct.\n                if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {\n                    yield this.deleteAllCaches();\n                    yield this.scope.registration.unregister();\n                    throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`);\n                }\n                // Cause the new version to become fully initialized. If this fails, then the\n                // version will not be available for use.\n                yield newVersion.initializeFully(this);\n                // Install this as an active version of the app.\n                this.versions.set(hash, newVersion);\n                // Future new clients will use this hash as the latest version.\n                this.latestHash = hash;\n                // If we are in `EXISTING_CLIENTS_ONLY` mode (meaning we didn't have a clean copy of the last\n                // latest version), we can now recover to `NORMAL` mode and start accepting new clients.\n                if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) {\n                    this.state = DriverReadyState.NORMAL;\n                    this.stateMessage = '(nominal)';\n                }\n                yield this.sync();\n                yield this.notifyClientsAboutUpdate(newVersion);\n            });\n        }\n        checkForUpdate() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                let hash = '(unknown)';\n                try {\n                    const manifest = yield this.fetchLatestManifest(true);\n                    if (manifest === null) {\n                        // Client or server offline. Unable to check for updates at this time.\n                        // Continue to service clients (existing and new).\n                        this.debugger.log('Check for update aborted. (Client or server offline.)');\n                        return false;\n                    }\n                    hash = hashManifest(manifest);\n                    // Check whether this is really an update.\n                    if (this.versions.has(hash)) {\n                        return false;\n                    }\n                    yield this.setupUpdate(manifest, hash);\n                    return true;\n                }\n                catch (err) {\n                    this.debugger.log(err, `Error occurred while updating to manifest ${hash}`);\n                    this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;\n                    this.stateMessage = `Degraded due to failed initialization: ${errorToString(err)}`;\n                    return false;\n                }\n            });\n        }\n        /**\n         * Synchronize the existing state to the underlying database.\n         */\n        sync() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Open up the DB table.\n                const table = yield this.db.open('control');\n                // Construct a serializable map of hashes to manifests.\n                const manifests = {};\n                this.versions.forEach((version, hash) => { manifests[hash] = version.manifest; });\n                // Construct a serializable map of client ids to version hashes.\n                const assignments = {};\n                this.clientVersionMap.forEach((hash, clientId) => { assignments[clientId] = hash; });\n                // Record the latest entry. Since this is a sync which is necessarily happening after\n                // initialization, latestHash should always be valid.\n                const latest = {\n                    latest: this.latestHash,\n                };\n                // Synchronize all of these.\n                yield Promise.all([\n                    table.write('manifests', manifests),\n                    table.write('assignments', assignments),\n                    table.write('latest', latest),\n                ]);\n            });\n        }\n        cleanupCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Query for all currently active clients, and list the client ids. This may skip\n                // some clients in the browser back-forward cache, but not much can be done about\n                // that.\n                const activeClients = (yield this.scope.clients.matchAll()).map(client => client.id);\n                // A simple list of client ids that the SW has kept track of. Subtracting\n                // activeClients from this list will result in the set of client ids which are\n                // being tracked but are no longer used in the browser, and thus can be cleaned up.\n                const knownClients = Array.from(this.clientVersionMap.keys());\n                // Remove clients in the clientVersionMap that are no longer active.\n                knownClients.filter(id => activeClients.indexOf(id) === -1)\n                    .forEach(id => this.clientVersionMap.delete(id));\n                // Next, determine the set of versions which are still used. All others can be\n                // removed.\n                const usedVersions = new Set();\n                this.clientVersionMap.forEach((version, _) => usedVersions.add(version));\n                // Collect all obsolete versions by filtering out used versions from the set of all versions.\n                const obsoleteVersions = Array.from(this.versions.keys())\n                    .filter(version => !usedVersions.has(version) && version !== this.latestHash);\n                // Remove all the versions which are no longer used.\n                yield obsoleteVersions.reduce((previous, version) => __awaiter$5(this, void 0, void 0, function* () {\n                    // Wait for the other cleanup operations to complete.\n                    yield previous;\n                    // Try to get past the failure of one particular version to clean up (this\n                    // shouldn't happen, but handle it just in case).\n                    try {\n                        // Get ahold of the AppVersion for this particular hash.\n                        const instance = this.versions.get(version);\n                        // Delete it from the canonical map.\n                        this.versions.delete(version);\n                        // Clean it up.\n                        yield instance.cleanup();\n                    }\n                    catch (err) {\n                        // Oh well? Not much that can be done here. These caches will be removed when\n                        // the SW revs its format version, which happens from time to time.\n                        this.debugger.log(err, `cleanupCaches - cleanup ${version}`);\n                    }\n                }), Promise.resolve());\n                // Commit all the changes to the saved state.\n                yield this.sync();\n            });\n        }\n        /**\n         * Delete caches that were used by older versions of `@angular/service-worker` to avoid running\n         * into storage quota limitations imposed by browsers.\n         * (Since at this point the SW has claimed all clients, it is safe to remove those caches.)\n         */\n        cleanupOldSwCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const cacheNames = yield this.scope.caches.keys();\n                const oldSwCacheNames = cacheNames.filter(name => /^ngsw:(?!\\/)/.test(name));\n                yield Promise.all(oldSwCacheNames.map(name => this.scope.caches.delete(name)));\n            });\n        }\n        /**\n         * Determine if a specific version of the given resource is cached anywhere within the SW,\n         * and fetch it if so.\n         */\n        lookupResourceWithHash(url, hash) {\n            return Array\n                // Scan through the set of all cached versions, valid or otherwise. It's safe to do such\n                // lookups even for invalid versions as the cached version of a resource will have the\n                // same hash regardless.\n                .from(this.versions.values())\n                // Reduce the set of versions to a single potential result. At any point along the\n                // reduction, if a response has already been identified, then pass it through, as no\n                // future operation could change the response. If no response has been found yet, keep\n                // checking versions until one is or until all versions have been exhausted.\n                .reduce((prev, version) => __awaiter$5(this, void 0, void 0, function* () {\n                // First, check the previous result. If a non-null result has been found already, just\n                // return it.\n                if ((yield prev) !== null) {\n                    return prev;\n                }\n                // No result has been found yet. Try the next `AppVersion`.\n                return version.lookupResourceWithHash(url, hash);\n            }), Promise.resolve(null));\n        }\n        lookupResourceWithoutHash(url) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const version = this.versions.get(this.latestHash);\n                return version ? version.lookupResourceWithoutHash(url) : null;\n            });\n        }\n        previouslyCachedResources() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const version = this.versions.get(this.latestHash);\n                return version ? version.previouslyCachedResources() : [];\n            });\n        }\n        recentCacheStatus(url) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const version = this.versions.get(this.latestHash);\n                return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED;\n            });\n        }\n        mergeHashWithAppData(manifest, hash) {\n            return {\n                hash,\n                appData: manifest.appData,\n            };\n        }\n        notifyClientsAboutUpdate(next) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const clients = yield this.scope.clients.matchAll();\n                yield clients.reduce((previous, client) => __awaiter$5(this, void 0, void 0, function* () {\n                    yield previous;\n                    // Firstly, determine which version this client is on.\n                    const version = this.clientVersionMap.get(client.id);\n                    if (version === undefined) {\n                        // Unmapped client - assume it's the latest.\n                        return;\n                    }\n                    if (version === this.latestHash) {\n                        // Client is already on the latest version, no need for a notification.\n                        return;\n                    }\n                    const current = this.versions.get(version);\n                    // Send a notice.\n                    const notice = {\n                        type: 'UPDATE_AVAILABLE',\n                        current: this.mergeHashWithAppData(current.manifest, version),\n                        available: this.mergeHashWithAppData(next.manifest, this.latestHash),\n                    };\n                    client.postMessage(notice);\n                }), Promise.resolve());\n            });\n        }\n        broadcast(msg) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const clients = yield this.scope.clients.matchAll();\n                clients.forEach(client => { client.postMessage(msg); });\n            });\n        }\n        debugState() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                return {\n                    state: DriverReadyState[this.state],\n                    why: this.stateMessage,\n                    latestHash: this.latestHash,\n                    lastUpdateCheck: this.lastUpdateCheck,\n                };\n            });\n        }\n        debugVersions() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Build list of versions.\n                return Array.from(this.versions.keys()).map(hash => {\n                    const version = this.versions.get(hash);\n                    const clients = Array.from(this.clientVersionMap.entries())\n                        .filter(([clientId, version]) => version === hash)\n                        .map(([clientId, version]) => clientId);\n                    return {\n                        hash,\n                        manifest: version.manifest, clients,\n                        status: '',\n                    };\n                });\n            });\n        }\n        debugIdleState() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                return {\n                    queue: this.idle.taskDescriptions,\n                    lastTrigger: this.idle.lastTrigger,\n                    lastRun: this.idle.lastRun,\n                };\n            });\n        }\n        safeFetch(req) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                try {\n                    return yield this.scope.fetch(req);\n                }\n                catch (err) {\n                    this.debugger.log(err, `Driver.fetch(${req.url})`);\n                    return this.adapter.newResponse(null, {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    const scope = self;\n    const adapter = new Adapter(scope);\n    const driver = new Driver(scope, adapter, new CacheDatabase(scope, adapter));\n\n}());\n"
  },
  {
    "path": "Chapter_12/HealthCheck/wwwroot/ngsw.json",
    "content": "{\n  \"configVersion\": 1,\n  \"timestamp\": 1577992220985,\n  \"index\": \"/index.html\",\n  \"assetGroups\": [\n    {\n      \"name\": \"app\",\n      \"installMode\": \"prefetch\",\n      \"updateMode\": \"prefetch\",\n      \"urls\": [\n        \"/index.html\",\n        \"/main-es2015.0dfa6088d7ebe21b943a.js\",\n        \"/main-es5.0dfa6088d7ebe21b943a.js\",\n        \"/manifest.webmanifest\",\n        \"/polyfills-es2015.58725a5910daef768ca8.js\",\n        \"/polyfills-es5.079443d8bcab7d711023.js\",\n        \"/runtime-es2015.e59a6cd8f1b6ab0c3f29.js\",\n        \"/runtime-es5.e59a6cd8f1b6ab0c3f29.js\",\n        \"/styles.519f5f6cb11c0ac03ff3.css\"\n      ],\n      \"patterns\": []\n    },\n    {\n      \"name\": \"assets\",\n      \"installMode\": \"lazy\",\n      \"updateMode\": \"prefetch\",\n      \"urls\": [\n        \"/assets/icons/icon-128x128.png\",\n        \"/assets/icons/icon-144x144.png\",\n        \"/assets/icons/icon-152x152.png\",\n        \"/assets/icons/icon-192x192.png\",\n        \"/assets/icons/icon-384x384.png\",\n        \"/assets/icons/icon-512x512.png\",\n        \"/assets/icons/icon-72x72.png\",\n        \"/assets/icons/icon-96x96.png\"\n      ],\n      \"patterns\": []\n    }\n  ],\n  \"dataGroups\": [],\n  \"hashTable\": {\n    \"/assets/icons/icon-128x128.png\": \"664808390a86b765b38840d2229bb8e77f23315f\",\n    \"/assets/icons/icon-144x144.png\": \"7d2df9f8dfadc3ba692838dc5a05fdda171ffe46\",\n    \"/assets/icons/icon-152x152.png\": \"328dddc298922fee24ca15f6188a8be20b1b5e85\",\n    \"/assets/icons/icon-192x192.png\": \"36caee50fcc2ddeca25acd0a74ccb12a04281dd2\",\n    \"/assets/icons/icon-384x384.png\": \"f3fe7601922932a95550a3fa767efec8eb2c54f2\",\n    \"/assets/icons/icon-512x512.png\": \"8e215e2e14a8fe8ed23ad313003a2da652b0acd9\",\n    \"/assets/icons/icon-72x72.png\": \"cbf6ac39ff640851fd721545fa68595aaf715e50\",\n    \"/assets/icons/icon-96x96.png\": \"745d24bafdf890ad4f63e9c4e69c713212849802\",\n    \"/index.html\": \"b73c833cf32b543e091e8683ded1744c3d7e0c76\",\n    \"/main-es2015.0dfa6088d7ebe21b943a.js\": \"2676c9e57672d9cec97ee29945ab8a60cf7dc916\",\n    \"/main-es5.0dfa6088d7ebe21b943a.js\": \"9332386d541e52f5269f53ca4be1aa8aa4576841\",\n    \"/manifest.webmanifest\": \"43ccf4b7574d245892754a78dcf31cfa4e2ef956\",\n    \"/polyfills-es2015.58725a5910daef768ca8.js\": \"3e29b382a75f751f979baff5606aeb37d3f66c3a\",\n    \"/polyfills-es5.079443d8bcab7d711023.js\": \"f2bfdffc3174ace137ac2516e8682571b6f460b0\",\n    \"/runtime-es2015.e59a6cd8f1b6ab0c3f29.js\": \"a9aafcf49f49145093fc831efd9b8e2f6c71bb9c\",\n    \"/runtime-es5.e59a6cd8f1b6ab0c3f29.js\": \"a9aafcf49f49145093fc831efd9b8e2f6c71bb9c\",\n    \"/styles.519f5f6cb11c0ac03ff3.css\": \"722333c0e596cfbb27969f7ac040dcef942da1ca\"\n  },\n  \"navigationUrls\": [\n    {\n      \"positive\": true,\n      \"regex\": \"^\\\\/.*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*\\\\.[^/]*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*__[^/]*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*__[^/]*\\\\/.*$\"\n    }\n  ]\n}"
  },
  {
    "path": "Chapter_12/HealthCheck/wwwroot/safety-worker.js",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\n\n// tslint:disable:no-console\n\nself.addEventListener('install', event => { self.skipWaiting(); });\n\nself.addEventListener('activate', event => {\n  event.waitUntil(self.clients.claim());\n  self.registration.unregister().then(\n      () => { console.log('NGSW Safety Worker - unregistered old service worker'); });\n});\n"
  },
  {
    "path": "Chapter_12/HealthCheck/wwwroot/test.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\" />\n    <title>Time for a test!</title>\n</head>\n<body>\n    Hello there!\n    <br /><br />\n    This is a test to see if the StaticFiles middleware is working properly.\n    <br /><br />\n    What about the client-side cache? Does it work or not?\n    <br /><br />\n    It seems like we can configure it: we disabled it during development,\n      and enabled it in production!\n</body>\n</html>"
  },
  {
    "path": "Chapter_12/WorldCities/.gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbuild/\nbld/\nbin/\nBin/\nobj/\nObj/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Microsoft Azure ApplicationInsights config file\nApplicationInsights.config\n\n# Windows Store app package directory\nAppPackages/\nBundleArtifacts/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n/node_modules\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\n\n# FAKE - F# Make\n.fake/\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled output\n/dist\n/dist-server\n/tmp\n/out-tsc\n\n# dependencies\n/node_modules\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# misc\n/.sass-cache\n/connect.lock\n/coverage\n/libpeerconnection.log\nnpm-debug.log\nyarn-error.log\ntestem.log\n/typings\n\n# System Files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/README.md",
    "content": "# WorldCities\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.0.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"WorldCities\": {\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"projectType\": \"application\",\n      \"prefix\": \"app\",\n      \"schematics\": {},\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"progress\": true,\n            \"extractCss\": true,\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.app.json\",\n            \"assets\": [\n              \"src/assets\",\n              \"src/manifest.webmanifest\"\n            ],\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"extractCss\": true,\n              \"namedChunks\": false,\n              \"aot\": true,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false,\n              \"buildOptimizer\": true,\n              \"serviceWorker\": true,\n              \"ngswConfigPath\": \"ngsw-config.json\"\n            }\n          }\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"WorldCities:build:production\"\n            }\n          }\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"WorldCities:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"src/tsconfig.spec.json\",\n            \"karmaConfig\": \"src/karma.conf.js\",\n            \"styles\": [\n              \"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css\",\n              \"src/styles.css\"\n            ],\n            \"scripts\": [],\n            \"assets\": [\n              \"src/assets\",\n              \"src/manifest.webmanifest\"\n            ]\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": [\n              \"src/tsconfig.app.json\",\n              \"src/tsconfig.spec.json\"\n            ],\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        },\n        \"server\": {\n          \"builder\": \"@angular-devkit/build-angular:server\",\n          \"options\": {\n            \"outputPath\": \"dist-server\",\n            \"main\": \"src/main.ts\",\n            \"tsConfig\": \"src/tsconfig.server.json\"\n          },\n          \"configurations\": {\n            \"dev\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": true\n            },\n            \"production\": {\n              \"optimization\": true,\n              \"outputHashing\": \"all\",\n              \"sourceMap\": false,\n              \"namedChunks\": false,\n              \"extractLicenses\": true,\n              \"vendorChunk\": false\n            }\n          }\n        }\n      }\n    },\n    \"WorldCities-e2e\": {\n      \"root\": \"e2e/\",\n      \"projectType\": \"application\",\n      \"architect\": {\n        \"e2e\": {\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\n          \"options\": {\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\n            \"devServerTarget\": \"WorldCities:serve\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\n          \"options\": {\n            \"tsConfig\": \"e2e/tsconfig.e2e.json\",\n            \"exclude\": [\n              \"**/node_modules/**\"\n            ]\n          }\n        }\n      }\n    }\n  },\n  \"defaultProject\": \"WorldCities\"\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/browserslist",
    "content": "# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n# For IE 9-11 support, please uncomment the last line of the file and adjust as needed\n> 0.5%\nlast 2 versions\nFirefox ESR\nnot dead\n# IE 9-11"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/e2e/protractor.conf.js",
    "content": "// Protractor configuration file, see link for more information\n// https://github.com/angular/protractor/blob/master/lib/config.ts\n\nconst { SpecReporter } = require(\"jasmine-spec-reporter\");\n\nexports.config = {\n  allScriptsTimeout: 11000,\n  specs: [\"./src/**/*.e2e-spec.ts\"],\n  capabilities: {\n    browserName: \"chrome\"\n  },\n  directConnect: true,\n  baseUrl: \"http://localhost:4200/\",\n  framework: \"jasmine\",\n  jasmineNodeOpts: {\n    showColors: true,\n    defaultTimeoutInterval: 30000,\n    print: function() {}\n  },\n  onPrepare() {\n    require(\"ts-node\").register({\n      project: require(\"path\").join(__dirname, \"./tsconfig.e2e.json\")\n    });\n    jasmine\n      .getEnv()\n      .addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));\n  }\n};\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/e2e/src/app.e2e-spec.ts",
    "content": "import { AppPage } from './app.po';\n\ndescribe('App', () => {\n  let page: AppPage;\n\n  beforeEach(() => {\n    page = new AppPage();\n  });\n\n  it('should display welcome message', () => {\n    page.navigateTo();\n    expect(page.getMainHeading()).toEqual('Hello, world!');\n  });\n});\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/e2e/src/app.po.ts",
    "content": "import { browser, by, element } from 'protractor';\n\nexport class AppPage {\n  navigateTo() {\n    return browser.get('/');\n  }\n\n  getMainHeading() {\n    return element(by.css('app-root h1')).getText();\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/e2e/tsconfig.e2e.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"module\": \"commonjs\",\n    \"target\": \"es5\",\n    \"types\": [\n      \"jasmine\",\n      \"jasminewd2\",\n      \"node\"\n    ]\n  }\n}"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/ngsw-config.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/service-worker/config/schema.json\",\n  \"index\": \"/index.html\",\n  \"assetGroups\": [\n    {\n      \"name\": \"app\",\n      \"installMode\": \"prefetch\",\n      \"resources\": {\n        \"files\": [\n          \"/favicon.ico\",\n          \"/index.html\",\n          \"/manifest.webmanifest\",\n          \"/*.css\",\n          \"/*.js\"\n        ]\n      }\n    }, {\n      \"name\": \"assets\",\n      \"installMode\": \"lazy\",\n      \"updateMode\": \"prefetch\",\n      \"resources\": {\n        \"files\": [\n          \"/assets/**\",\n          \"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)\"\n        ]\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/package.json",
    "content": "{\n  \"name\": \"worldcities\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"echo Starting... && ng serve\",\n    \"build\": \"ng build\",\n    \"build:ssr\": \"ng run WorldCities:server:dev\",\n    \"test\": \"ng test\",\n    \"lint\": \"ng lint\",\n    \"e2e\": \"ng e2e\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"9.0.0\",\n    \"@angular/cdk\": \"9.0.0\",\n    \"@angular/common\": \"9.0.0\",\n    \"@angular/compiler\": \"9.0.0\",\n    \"@angular/core\": \"9.0.0\",\n    \"@angular/forms\": \"9.0.0\",\n    \"@angular/material\": \"9.0.0\",\n    \"@angular/platform-browser\": \"9.0.0\",\n    \"@angular/platform-browser-dynamic\": \"9.0.0\",\n    \"@angular/platform-server\": \"9.0.0\",\n    \"@angular/pwa\": \"^0.803.21\",\n    \"@angular/router\": \"9.0.0\",\n    \"@angular/service-worker\": \"9.0.0\",\n    \"@nguniversal/module-map-ngfactory-loader\": \"9.0.0-next.9\",\n    \"aspnet-prerendering\": \"3.0.1\",\n    \"bootstrap\": \"4.4.1\",\n    \"core-js\": \"3.6.1\",\n    \"hammerjs\": \"2.0.8\",\n    \"jquery\": \"3.5.1\",\n    \"oidc-client\": \"1.9.1\",\n    \"popper.js\": \"1.16.0\",\n    \"rxjs\": \"6.5.4\",\n    \"zone.js\": \"0.10.2\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"0.900.0\",\n    \"@angular/cli\": \"9.0.0\",\n    \"@angular/compiler-cli\": \"9.0.0\",\n    \"@angular/language-service\": \"9.0.0\",\n    \"@types/jasmine\": \"3.5.0\",\n    \"@types/jasminewd2\": \"2.0.8\",\n    \"@types/node\": \"13.1.1\",\n    \"codelyzer\": \"5.2.1\",\n    \"jasmine-core\": \"3.5.0\",\n    \"jasmine-spec-reporter\": \"4.2.1\",\n    \"karma\": \"4.4.1\",\n    \"karma-chrome-launcher\": \"3.1.0\",\n    \"karma-coverage-istanbul-reporter\": \"2.1.1\",\n    \"karma-jasmine\": \"2.0.1\",\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\n    \"typescript\": \"3.7.5\"\n  },\n  \"optionalDependencies\": {\n    \"node-sass\": \"4.13.0\",\n    \"protractor\": \"5.4.2\",\n    \"ts-node\": \"5.0.1\",\n    \"tslint\": \"5.20.1\"\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/api-authorization.constants.ts",
    "content": "export const ApplicationName = 'WorldCities';\n\nexport const ReturnUrlType = 'returnUrl';\n\nexport const QueryParameterNames = {\n  ReturnUrl: ReturnUrlType,\n  Message: 'message'\n};\n\nexport const LogoutActions = {\n  LogoutCallback: 'logout-callback',\n  Logout: 'logout',\n  LoggedOut: 'logged-out'\n};\n\nexport const LoginActions = {\n  Login: 'login',\n  LoginCallback: 'login-callback',\n  LoginFailed: 'login-failed',\n  Profile: 'profile',\n  Register: 'register'\n};\n\nlet applicationPaths: ApplicationPathsType = {\n  DefaultLoginRedirectPath: '/',\n  ApiAuthorizationClientConfigurationUrl: `/_configuration/${ApplicationName}`,\n  Login: `authentication/${LoginActions.Login}`,\n  LoginFailed: `authentication/${LoginActions.LoginFailed}`,\n  LoginCallback: `authentication/${LoginActions.LoginCallback}`,\n  Register: `authentication/${LoginActions.Register}`,\n  Profile: `authentication/${LoginActions.Profile}`,\n  LogOut: `authentication/${LogoutActions.Logout}`,\n  LoggedOut: `authentication/${LogoutActions.LoggedOut}`,\n  LogOutCallback: `authentication/${LogoutActions.LogoutCallback}`,\n  LoginPathComponents: [],\n  LoginFailedPathComponents: [],\n  LoginCallbackPathComponents: [],\n  RegisterPathComponents: [],\n  ProfilePathComponents: [],\n  LogOutPathComponents: [],\n  LoggedOutPathComponents: [],\n  LogOutCallbackPathComponents: [],\n  IdentityRegisterPath: '/Identity/Account/Register',\n  IdentityManagePath: '/Identity/Account/Manage'\n};\n\napplicationPaths = {\n  ...applicationPaths,\n  LoginPathComponents: applicationPaths.Login.split('/'),\n  LoginFailedPathComponents: applicationPaths.LoginFailed.split('/'),\n  RegisterPathComponents: applicationPaths.Register.split('/'),\n  ProfilePathComponents: applicationPaths.Profile.split('/'),\n  LogOutPathComponents: applicationPaths.LogOut.split('/'),\n  LoggedOutPathComponents: applicationPaths.LoggedOut.split('/'),\n  LogOutCallbackPathComponents: applicationPaths.LogOutCallback.split('/')\n};\n\ninterface ApplicationPathsType {\n  readonly DefaultLoginRedirectPath: string;\n  readonly ApiAuthorizationClientConfigurationUrl: string;\n  readonly Login: string;\n  readonly LoginFailed: string;\n  readonly LoginCallback: string;\n  readonly Register: string;\n  readonly Profile: string;\n  readonly LogOut: string;\n  readonly LoggedOut: string;\n  readonly LogOutCallback: string;\n  readonly LoginPathComponents: string [];\n  readonly LoginFailedPathComponents: string [];\n  readonly LoginCallbackPathComponents: string [];\n  readonly RegisterPathComponents: string [];\n  readonly ProfilePathComponents: string [];\n  readonly LogOutPathComponents: string [];\n  readonly LoggedOutPathComponents: string [];\n  readonly LogOutCallbackPathComponents: string [];\n  readonly IdentityRegisterPath: string;\n  readonly IdentityManagePath: string;\n}\n\nexport const ApplicationPaths: ApplicationPathsType = applicationPaths;\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/api-authorization.module.spec.ts",
    "content": "import { ApiAuthorizationModule } from './api-authorization.module';\n\ndescribe('ApiAuthorizationModule', () => {\n  let apiAuthorizationModule: ApiAuthorizationModule;\n\n  beforeEach(() => {\n    apiAuthorizationModule = new ApiAuthorizationModule();\n  });\n\n  it('should create an instance', () => {\n    expect(apiAuthorizationModule).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/api-authorization.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { LoginMenuComponent } from './login-menu/login-menu.component';\nimport { LoginComponent } from './login/login.component';\nimport { LogoutComponent } from './logout/logout.component';\nimport { RouterModule } from '@angular/router';\nimport { ApplicationPaths } from './api-authorization.constants';\nimport { HttpClientModule } from '@angular/common/http';\n\n@NgModule({\n  imports: [\n    CommonModule,\n    HttpClientModule,\n    RouterModule.forChild(\n      [\n        { path: ApplicationPaths.Register, component: LoginComponent },\n        { path: ApplicationPaths.Profile, component: LoginComponent },\n        { path: ApplicationPaths.Login, component: LoginComponent },\n        { path: ApplicationPaths.LoginFailed, component: LoginComponent },\n        { path: ApplicationPaths.LoginCallback, component: LoginComponent },\n        { path: ApplicationPaths.LogOut, component: LogoutComponent },\n        { path: ApplicationPaths.LoggedOut, component: LogoutComponent },\n        { path: ApplicationPaths.LogOutCallback, component: LogoutComponent }\n      ]\n    )\n  ],\n  declarations: [LoginMenuComponent, LoginComponent, LogoutComponent],\n  exports: [LoginMenuComponent, LoginComponent, LogoutComponent]\n})\nexport class ApiAuthorizationModule { }\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/authorize.guard.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeGuard } from './authorize.guard';\n\ndescribe('AuthorizeGuard', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeGuard]\n    });\n  });\n\n  it('should ...', inject([AuthorizeGuard], (guard: AuthorizeGuard) => {\n    expect(guard).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/authorize.guard.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { AuthorizeService } from './authorize.service';\nimport { tap } from 'rxjs/operators';\nimport { ApplicationPaths, QueryParameterNames } from './api-authorization.constants';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeGuard implements CanActivate {\n  constructor(private authorize: AuthorizeService, private router: Router) {\n  }\n  canActivate(\n    _next: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {\n      return this.authorize.isAuthenticated()\n        .pipe(tap(isAuthenticated => this.handleAuthorization(isAuthenticated, state)));\n  }\n\n  private handleAuthorization(isAuthenticated: boolean, state: RouterStateSnapshot) {\n    if (!isAuthenticated) {\n      this.router.navigate(ApplicationPaths.LoginPathComponents, {\n        queryParams: {\n          [QueryParameterNames.ReturnUrl]: state.url\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/authorize.interceptor.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeInterceptor } from './authorize.interceptor';\n\ndescribe('AuthorizeInterceptor', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeInterceptor]\n    });\n  });\n\n  it('should be created', inject([AuthorizeInterceptor], (service: AuthorizeInterceptor) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/authorize.interceptor.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';\nimport { Observable } from 'rxjs';\nimport { AuthorizeService } from './authorize.service';\nimport { mergeMap } from 'rxjs/operators';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeInterceptor implements HttpInterceptor {\n  constructor(private authorize: AuthorizeService) { }\n\n  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {\n    return this.authorize.getAccessToken()\n      .pipe(mergeMap(token => this.processRequestWithToken(token, req, next)));\n  }\n\n  // Checks if there is an access_token available in the authorize service\n  // and adds it to the request in case it's targeted at the same origin as the\n  // single page application.\n  private processRequestWithToken(token: string, req: HttpRequest<any>, next: HttpHandler) {\n    if (!!token && this.isSameOriginUrl(req)) {\n      req = req.clone({\n        setHeaders: {\n          Authorization: `Bearer ${token}`\n        }\n      });\n    }\n\n    return next.handle(req);\n  }\n\n  private isSameOriginUrl(req: any) {\n    // It's an absolute url with the same origin.\n    if (req.url.startsWith(`${window.location.origin}/`)) {\n      return true;\n    }\n\n    // It's a protocol relative url with the same origin.\n    // For example: //www.example.com/api/Products\n    if (req.url.startsWith(`//${window.location.host}/`)) {\n      return true;\n    }\n\n    // It's a relative url like /api/Products\n    if (/^\\/[^\\/].*/.test(req.url)) {\n      return true;\n    }\n\n    // It's an absolute or protocol relative url that\n    // doesn't have the same origin.\n    return false;\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/authorize.service.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { AuthorizeService } from './authorize.service';\n\ndescribe('AuthorizeService', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [AuthorizeService]\n    });\n  });\n\n  it('should be created', inject([AuthorizeService], (service: AuthorizeService) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/authorize.service.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { User, UserManager, WebStorageStateStore } from 'oidc-client';\nimport { BehaviorSubject, concat, from, Observable } from 'rxjs';\nimport { filter, map, mergeMap, take, tap } from 'rxjs/operators';\nimport { ApplicationPaths, ApplicationName } from './api-authorization.constants';\n\nexport type IAuthenticationResult =\n  SuccessAuthenticationResult |\n  FailureAuthenticationResult |\n  RedirectAuthenticationResult;\n\nexport interface SuccessAuthenticationResult {\n  status: AuthenticationResultStatus.Success;\n  state: any;\n}\n\nexport interface FailureAuthenticationResult {\n  status: AuthenticationResultStatus.Fail;\n  message: string;\n}\n\nexport interface RedirectAuthenticationResult {\n  status: AuthenticationResultStatus.Redirect;\n}\n\nexport enum AuthenticationResultStatus {\n  Success,\n  Redirect,\n  Fail\n}\n\nexport interface IUser {\n  name: string;\n}\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class AuthorizeService {\n  // By default pop ups are disabled because they don't work properly on Edge.\n  // If you want to enable pop up authentication simply set this flag to false.\n\n  private popUpDisabled = true;\n  private userManager: UserManager;\n  private userSubject: BehaviorSubject<IUser | null> = new BehaviorSubject(null);\n\n  public isAuthenticated(): Observable<boolean> {\n    return this.getUser().pipe(map(u => !!u));\n  }\n\n  public getUser(): Observable<IUser | null> {\n    return concat(\n      this.userSubject.pipe(take(1), filter(u => !!u)),\n      this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))),\n      this.userSubject.asObservable());\n  }\n\n  public getAccessToken(): Observable<string> {\n    return from(this.ensureUserManagerInitialized())\n      .pipe(mergeMap(() => from(this.userManager.getUser())),\n        map(user => user && user.access_token));\n  }\n\n  // We try to authenticate the user in three different ways:\n  // 1) We try to see if we can authenticate the user silently. This happens\n  //    when the user is already logged in on the IdP and is done using a hidden iframe\n  //    on the client.\n  // 2) We try to authenticate the user using a PopUp Window. This might fail if there is a\n  //    Pop-Up blocker or the user has disabled PopUps.\n  // 3) If the two methods above fail, we redirect the browser to the IdP to perform a traditional\n  //    redirect flow.\n  public async signIn(state: any): Promise<IAuthenticationResult> {\n    await this.ensureUserManagerInitialized();\n    let user: User = null;\n    try {\n      user = await this.userManager.signinSilent(this.createArguments());\n      this.userSubject.next(user.profile);\n      return this.success(state);\n    } catch (silentError) {\n      // User might not be authenticated, fallback to popup authentication\n      console.log('Silent authentication error: ', silentError);\n\n      try {\n        if (this.popUpDisabled) {\n          throw new Error('Popup disabled. Change \\'authorize.service.ts:AuthorizeService.popupDisabled\\' to false to enable it.');\n        }\n        user = await this.userManager.signinPopup(this.createArguments());\n        this.userSubject.next(user.profile);\n        return this.success(state);\n      } catch (popupError) {\n        if (popupError.message === 'Popup window closed') {\n          // The user explicitly cancelled the login action by closing an opened popup.\n          return this.error('The user closed the window.');\n        } else if (!this.popUpDisabled) {\n          console.log('Popup authentication error: ', popupError);\n        }\n\n        // PopUps might be blocked by the user, fallback to redirect\n        try {\n          await this.userManager.signinRedirect(this.createArguments(state));\n          return this.redirect();\n        } catch (redirectError) {\n          console.log('Redirect authentication error: ', redirectError);\n          return this.error(redirectError);\n        }\n      }\n    }\n  }\n\n  public async completeSignIn(url: string): Promise<IAuthenticationResult> {\n    try {\n      await this.ensureUserManagerInitialized();\n      const user = await this.userManager.signinCallback(url);\n      this.userSubject.next(user && user.profile);\n      return this.success(user && user.state);\n    } catch (error) {\n      console.log('There was an error signing in: ', error);\n      return this.error('There was an error signing in.');\n    }\n  }\n\n  public async signOut(state: any): Promise<IAuthenticationResult> {\n    try {\n      if (this.popUpDisabled) {\n        throw new Error('Popup disabled. Change \\'authorize.service.ts:AuthorizeService.popupDisabled\\' to false to enable it.');\n      }\n\n      await this.ensureUserManagerInitialized();\n      await this.userManager.signoutPopup(this.createArguments());\n      this.userSubject.next(null);\n      return this.success(state);\n    } catch (popupSignOutError) {\n      console.log('Popup signout error: ', popupSignOutError);\n      try {\n        await this.userManager.signoutRedirect(this.createArguments(state));\n        return this.redirect();\n      } catch (redirectSignOutError) {\n        console.log('Redirect signout error: ', popupSignOutError);\n        return this.error(redirectSignOutError);\n      }\n    }\n  }\n\n  public async completeSignOut(url: string): Promise<IAuthenticationResult> {\n    await this.ensureUserManagerInitialized();\n    try {\n      const state = await this.userManager.signoutCallback(url);\n      this.userSubject.next(null);\n      return this.success(state && state.data);\n    } catch (error) {\n      console.log(`There was an error trying to log out '${error}'.`);\n      return this.error(error);\n    }\n  }\n\n  private createArguments(state?: any): any {\n    return { useReplaceToNavigate: true, data: state };\n  }\n\n  private error(message: string): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Fail, message };\n  }\n\n  private success(state: any): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Success, state };\n  }\n\n  private redirect(): IAuthenticationResult {\n    return { status: AuthenticationResultStatus.Redirect };\n  }\n\n  private async ensureUserManagerInitialized(): Promise<void> {\n    if (this.userManager !== undefined) {\n      return;\n    }\n\n    const response = await fetch(ApplicationPaths.ApiAuthorizationClientConfigurationUrl);\n    if (!response.ok) {\n      throw new Error(`Could not load settings for '${ApplicationName}'`);\n    }\n\n    const settings: any = await response.json();\n    settings.automaticSilentRenew = true;\n    settings.includeIdTokenInSilentRenew = true;\n    this.userManager = new UserManager(settings);\n\n    this.userManager.events.addUserSignedOut(async () => {\n      await this.userManager.removeUser();\n      this.userSubject.next(null);\n    });\n  }\n\n  private getUserFromStorage(): Observable<IUser> {\n    return from(this.ensureUserManagerInitialized())\n      .pipe(\n        mergeMap(() => this.userManager.getUser()),\n        map(u => u && u.profile));\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/login/login.component.css",
    "content": ""
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/login/login.component.html",
    "content": "<p>{{ message | async }}</p>"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/login/login.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LoginComponent } from './login.component';\n\ndescribe('LoginComponent', () => {\n  let component: LoginComponent;\n  let fixture: ComponentFixture<LoginComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LoginComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LoginComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/login/login.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthorizeService, AuthenticationResultStatus } from '../authorize.service';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { BehaviorSubject } from 'rxjs';\nimport { LoginActions, QueryParameterNames, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants';\n\n// The main responsibility of this component is to handle the user's login process.\n// This is the starting point for the login process. Any component that needs to authenticate\n// a user can simply perform a redirect to this component with a returnUrl query parameter and\n// let the component perform the login and return back to the return url.\n@Component({\n  selector: 'app-login',\n  templateUrl: './login.component.html',\n  styleUrls: ['./login.component.css']\n})\nexport class LoginComponent implements OnInit {\n  public message = new BehaviorSubject<string>(null);\n\n  constructor(\n    private authorizeService: AuthorizeService,\n    private activatedRoute: ActivatedRoute,\n    private router: Router) { }\n\n  async ngOnInit() {\n    const action = this.activatedRoute.snapshot.url[1];\n    switch (action.path) {\n      case LoginActions.Login:\n        await this.login(this.getReturnUrl());\n        break;\n      case LoginActions.LoginCallback:\n        await this.processLoginCallback();\n        break;\n      case LoginActions.LoginFailed:\n        const message = this.activatedRoute.snapshot.queryParamMap.get(QueryParameterNames.Message);\n        this.message.next(message);\n        break;\n      case LoginActions.Profile:\n        this.redirectToProfile();\n        break;\n      case LoginActions.Register:\n        this.redirectToRegister();\n        break;\n      default:\n        throw new Error(`Invalid action '${action}'`);\n    }\n  }\n\n\n  private async login(returnUrl: string): Promise<void> {\n    const state: INavigationState = { returnUrl };\n    const result = await this.authorizeService.signIn(state);\n    this.message.next(undefined);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        break;\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(returnUrl);\n        break;\n      case AuthenticationResultStatus.Fail:\n        await this.router.navigate(ApplicationPaths.LoginFailedPathComponents, {\n          queryParams: { [QueryParameterNames.Message]: result.message }\n        });\n        break;\n      default:\n        throw new Error(`Invalid status result ${(result as any).status}.`);\n    }\n  }\n\n  private async processLoginCallback(): Promise<void> {\n    const url = window.location.href;\n    const result = await this.authorizeService.completeSignIn(url);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        // There should not be any redirects as completeSignIn never redirects.\n        throw new Error('Should not redirect.');\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(this.getReturnUrl(result.state));\n        break;\n      case AuthenticationResultStatus.Fail:\n        this.message.next(result.message);\n        break;\n    }\n  }\n\n  private redirectToRegister(): any {\n    this.redirectToApiAuthorizationPath(\n      `${ApplicationPaths.IdentityRegisterPath}?returnUrl=${encodeURI('/' + ApplicationPaths.Login)}`);\n  }\n\n  private redirectToProfile(): void {\n    this.redirectToApiAuthorizationPath(ApplicationPaths.IdentityManagePath);\n  }\n\n  private async navigateToReturnUrl(returnUrl: string) {\n    // It's important that we do a replace here so that we remove the callback uri with the\n    // fragment containing the tokens from the browser history.\n    await this.router.navigateByUrl(returnUrl, {\n      replaceUrl: true\n    });\n  }\n\n  private getReturnUrl(state?: INavigationState): string {\n    const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl;\n    // If the url is comming from the query string, check that is either\n    // a relative url or an absolute url\n    if (fromQuery &&\n      !(fromQuery.startsWith(`${window.location.origin}/`) ||\n        /\\/[^\\/].*/.test(fromQuery))) {\n      // This is an extra check to prevent open redirects.\n      throw new Error('Invalid return url. The return url needs to have the same origin as the current page.');\n    }\n    return (state && state.returnUrl) ||\n      fromQuery ||\n      ApplicationPaths.DefaultLoginRedirectPath;\n  }\n\n  private redirectToApiAuthorizationPath(apiAuthorizationPath: string) {\n    // It's important that we do a replace here so that when the user hits the back arrow on the\n    // browser they get sent back to where it was on the app instead of to an endpoint on this\n    // component.\n    const redirectUrl = `${window.location.origin}${apiAuthorizationPath}`;\n    window.location.replace(redirectUrl);\n  }\n}\n\ninterface INavigationState {\n  [ReturnUrlType]: string;\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.css",
    "content": ""
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.html",
    "content": "<ul class=\"navbar-nav\" *ngIf=\"isAuthenticated | async\">\n    <li class=\"nav-item\">\n        <a  class=\"nav-link text-dark\" [routerLink]='[\"/authentication/profile\"]' title=\"Manage\">Hello {{ userName | async }}</a>\n    </li>\n    <li class=\"nav-item\">\n        <a  class=\"nav-link text-dark\" [routerLink]='[\"/authentication/logout\"]' [state]='{ local: true }' title=\"Logout\">Logout</a>\n    </li>\n</ul>\n<ul class=\"navbar-nav\" *ngIf=\"!(isAuthenticated | async)\">\n  <li class=\"nav-item\">\n        <a class=\"nav-link text-dark\" [routerLink]='[\"/authentication/register\"]'>Register</a>\n    </li>\n    <li class=\"nav-item\">\n        <a class=\"nav-link text-dark\" [routerLink]='[\"/authentication/login\"]'>Login</a>\n    </li>\n</ul>\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LoginMenuComponent } from './login-menu.component';\n\ndescribe('LoginMenuComponent', () => {\n  let component: LoginMenuComponent;\n  let fixture: ComponentFixture<LoginMenuComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LoginMenuComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LoginMenuComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/login-menu/login-menu.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthorizeService } from '../authorize.service';\nimport { Observable } from 'rxjs';\nimport { map, tap } from 'rxjs/operators';\n\n@Component({\n  selector: 'app-login-menu',\n  templateUrl: './login-menu.component.html',\n  styleUrls: ['./login-menu.component.css']\n})\nexport class LoginMenuComponent implements OnInit {\n  public isAuthenticated: Observable<boolean>;\n  public userName: Observable<string>;\n\n  constructor(private authorizeService: AuthorizeService) { }\n\n  ngOnInit() {\n    this.isAuthenticated = this.authorizeService.isAuthenticated();\n    this.userName = this.authorizeService.getUser().pipe(map(u => u && u.name));\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/logout/logout.component.css",
    "content": ""
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/logout/logout.component.html",
    "content": "<p>{{ message | async }}</p>"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/logout/logout.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\n\nimport { LogoutComponent } from './logout.component';\n\ndescribe('LogoutComponent', () => {\n  let component: LogoutComponent;\n  let fixture: ComponentFixture<LogoutComponent>;\n\n  beforeEach(async(() => {\n    TestBed.configureTestingModule({\n      declarations: [ LogoutComponent ]\n    })\n    .compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(LogoutComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/api-authorization/logout/logout.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { AuthenticationResultStatus, AuthorizeService } from '../authorize.service';\nimport { BehaviorSubject } from 'rxjs';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { take } from 'rxjs/operators';\nimport { LogoutActions, ApplicationPaths, ReturnUrlType } from '../api-authorization.constants';\n\n// The main responsibility of this component is to handle the user's logout process.\n// This is the starting point for the logout process, which is usually initiated when a\n// user clicks on the logout button on the LoginMenu component.\n@Component({\n  selector: 'app-logout',\n  templateUrl: './logout.component.html',\n  styleUrls: ['./logout.component.css']\n})\nexport class LogoutComponent implements OnInit {\n  public message = new BehaviorSubject<string>(null);\n\n  constructor(\n    private authorizeService: AuthorizeService,\n    private activatedRoute: ActivatedRoute,\n    private router: Router) { }\n\n  async ngOnInit() {\n    const action = this.activatedRoute.snapshot.url[1];\n    switch (action.path) {\n      case LogoutActions.Logout:\n        if (!!window.history.state.local) {\n          await this.logout(this.getReturnUrl());\n        } else {\n          // This prevents regular links to <app>/authentication/logout from triggering a logout\n          this.message.next('The logout was not initiated from within the page.');\n        }\n\n        break;\n      case LogoutActions.LogoutCallback:\n        await this.processLogoutCallback();\n        break;\n      case LogoutActions.LoggedOut:\n        this.message.next('You successfully logged out!');\n        break;\n      default:\n        throw new Error(`Invalid action '${action}'`);\n    }\n  }\n\n  private async logout(returnUrl: string): Promise<void> {\n    const state: INavigationState = { returnUrl };\n    const isauthenticated = await this.authorizeService.isAuthenticated().pipe(\n      take(1)\n    ).toPromise();\n    if (isauthenticated) {\n      const result = await this.authorizeService.signOut(state);\n      switch (result.status) {\n        case AuthenticationResultStatus.Redirect:\n          break;\n        case AuthenticationResultStatus.Success:\n          await this.navigateToReturnUrl(returnUrl);\n          break;\n        case AuthenticationResultStatus.Fail:\n          this.message.next(result.message);\n          break;\n        default:\n          throw new Error('Invalid authentication result status.');\n      }\n    } else {\n      this.message.next('You successfully logged out!');\n    }\n  }\n\n  private async processLogoutCallback(): Promise<void> {\n    const url = window.location.href;\n    const result = await this.authorizeService.completeSignOut(url);\n    switch (result.status) {\n      case AuthenticationResultStatus.Redirect:\n        // There should not be any redirects as the only time completeAuthentication finishes\n        // is when we are doing a redirect sign in flow.\n        throw new Error('Should not redirect.');\n      case AuthenticationResultStatus.Success:\n        await this.navigateToReturnUrl(this.getReturnUrl(result.state));\n        break;\n      case AuthenticationResultStatus.Fail:\n        this.message.next(result.message);\n        break;\n      default:\n        throw new Error('Invalid authentication result status.');\n    }\n  }\n\n  private async navigateToReturnUrl(returnUrl: string) {\n    await this.router.navigateByUrl(returnUrl, {\n      replaceUrl: true\n    });\n  }\n\n  private getReturnUrl(state?: INavigationState): string {\n    const fromQuery = (this.activatedRoute.snapshot.queryParams as INavigationState).returnUrl;\n    // If the url is comming from the query string, check that is either\n    // a relative url or an absolute url\n    if (fromQuery &&\n      !(fromQuery.startsWith(`${window.location.origin}/`) ||\n        /\\/[^\\/].*/.test(fromQuery))) {\n      // This is an extra check to prevent open redirects.\n      throw new Error('Invalid return url. The return url needs to have the same origin as the current page.');\n    }\n    return (state && state.returnUrl) ||\n      fromQuery ||\n      ApplicationPaths.LoggedOut;\n  }\n}\n\ninterface INavigationState {\n  [ReturnUrlType]: string;\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/angular-material.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatPaginatorModule } from '@angular/material/paginator';\nimport { MatSortModule } from '@angular/material/sort';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\n\n@NgModule({\n  imports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ],\n  exports: [\n    MatTableModule,\n    MatPaginatorModule,\n    MatSortModule,\n    MatInputModule,\n    MatSelectModule\n  ]\n})\n\nexport class AngularMaterialModule { }\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/app.component.html",
    "content": "<body>\n\n  <div class=\"alert alert-warning\" *ngIf=\"!isConnected\">\n    <strong>WARNING</strong>: the app is currently <i>offline</i>:\n    some features that rely upon the back-end might not work as expected.\n    This message will automatically disappear\n    as soon as the internet connection becomes available again.\n  </div>\n\n  <app-nav-menu></app-nav-menu>\n  <div class=\"container\">\n    <router-outlet></router-outlet>\n  </div>\n</body>\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { ConnectionService } from '../ng-connection-service/connection-service.service';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html'\n})\nexport class AppComponent {\n  title = 'app';\n\n  hasNetworkConnection: boolean;\n  hasInternetAccess: boolean;\n  isConnected: boolean;\n  status: string;\n\n  constructor(private connectionService: ConnectionService) {\n    this.connectionService.updateOptions({\n      heartbeatUrl: \"/isOnline.txt\"\n    });\n    this.connectionService.monitor().subscribe(currentState => {\n      this.hasNetworkConnection = currentState.hasNetworkConnection;\n      this.hasInternetAccess = currentState.hasInternetAccess;\n      if (this.hasNetworkConnection && this.hasInternetAccess) {\n        this.isConnected = true;\n        this.status = 'ONLINE';\n      }\n      else {\n        this.isConnected = false;\n        this.status = 'OFFLINE';\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/app.module.ts",
    "content": "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';\nimport { RouterModule } from '@angular/router';\n\nimport { AppComponent } from './app.component';\nimport { BaseFormComponent } from './base.form.component';\nimport { NavMenuComponent } from './nav-menu/nav-menu.component';\nimport { HomeComponent } from './home/home.component';\nimport { CitiesComponent } from './cities/cities.component';\nimport { CityEditComponent } from './cities/city-edit.component';\nimport { CountriesComponent } from './countries/countries.component';\nimport { CountryEditComponent } from './countries/country-edit.component';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { AngularMaterialModule } from './angular-material.module';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\n\nimport { ApiAuthorizationModule } from 'src/api-authorization/api-authorization.module';\nimport { AuthorizeGuard } from 'src/api-authorization/authorize.guard';\nimport { AuthorizeInterceptor } from 'src/api-authorization/authorize.interceptor';\n\nimport { ServiceWorkerModule } from '@angular/service-worker';\nimport { environment } from '../environments/environment';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    BaseFormComponent,\n    NavMenuComponent,\n    HomeComponent,\n    CitiesComponent,\n    CityEditComponent,\n    CountriesComponent,\n    CountryEditComponent\n  ],\n  imports: [\n    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),\n    HttpClientModule,\n    FormsModule,\n    ApiAuthorizationModule,\n    RouterModule.forRoot([\n      {\n        path: '',\n        component: HomeComponent,\n        pathMatch: 'full'\n      },\n      {\n        path: 'cities',\n        component: CitiesComponent\n      },\n      {\n        path: 'city/:id',\n        component: CityEditComponent,\n        canActivate: [AuthorizeGuard]\n      },\n      {\n        path: 'city',\n        component: CityEditComponent,\n        canActivate: [AuthorizeGuard]\n      },\n      {\n        path: 'countries',\n        component: CountriesComponent\n      },\n      {\n        path: 'country/:id',\n        component: CountryEditComponent,\n        canActivate: [AuthorizeGuard]\n      },\n      {\n        path: 'country',\n        component: CountryEditComponent,\n        canActivate: [AuthorizeGuard]\n      }\n    ]),\n    BrowserAnimationsModule,\n    AngularMaterialModule,\n    ReactiveFormsModule,\n    ServiceWorkerModule.register(\n      'ngsw-worker.js',\n      {\n        registrationStrategy: 'registerImmediately'\n      })\n    ],\n  providers: [\n    {\n      provide: HTTP_INTERCEPTORS,\n      useClass: AuthorizeInterceptor,\n      multi: true\n    }\n  ],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/app.server.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { ServerModule } from '@angular/platform-server';\nimport { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';\nimport { AppComponent } from './app.component';\nimport { AppModule } from './app.module';\n\n@NgModule({\n    imports: [AppModule, ServerModule, ModuleMapLoaderModule],\n    bootstrap: [AppComponent]\n})\nexport class AppServerModule { }\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/base.form.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { FormGroup } from '@angular/forms';\n\n@Component({\n    template: ''\n})  \nexport class BaseFormComponent {\n\n    // the form model\n    form: FormGroup;\n\n    constructor() {\n    }\n\n    // retrieve a FormControl\n    getControl(name: string) {\n        return this.form.get(name);\n    }\n\n    // returns TRUE if the FormControl is valid\n    isValid(name: string) {\n        var e = this.getControl(name);\n        return e && e.valid;\n    }\n\n    // returns TRUE if the FormControl has been changed\n    isChanged(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched);\n    }\n\n    // returns TRUE if the FormControl is raising an error,\n    // i.e. an invalid state after user changes\n    hasError(name: string) {\n        var e = this.getControl(name);\n        return e && (e.dirty || e.touched) && e.invalid;\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/base.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Observable } from 'rxjs';\n\n@Injectable()\nexport abstract class BaseService {\n    constructor(\n        public http: HttpClient,\n        public baseUrl: string\n    ) {\n    }\n\n    abstract getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string): Observable<ApiResult>;\n\n    abstract get<T>(id: number): Observable<T>;\n    abstract put<T>(item: T): Observable<T>;\n    abstract post<T>(item: T): Observable<T>;\n}\n\nexport interface ApiResult<T> {\n    data: T[];\n    pageIndex: number;\n    pageSize: number;\n    totalCount: number;\n    totalPages: number;\n    sortColumn: string;\n    sortOrder: string;\n    filterColumn: string;\n    filterQuery: string;\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/cities/cities.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/cities/cities.component.html",
    "content": "<h1>Cities</h1>\n\n<p>Here's a list of cities: feel free to play with it.</p>\n\n<p *ngIf=\"!cities\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"cities\">\n  <button type=\"button\"\n          [routerLink]=\"['/city']\"\n          class=\"btn btn-success\">\n      Add a new City\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!cities\">\n    <input matInput (keyup)=\"loadData($event.target.value)\"\n        placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"cities\" class=\"mat-elevation-z8\" [hidden]=\"!cities\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/city', city.id]\">{{city.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"lat\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Latitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lat}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"lon\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Longitude</th>\n    <td mat-cell *matCellDef=\"let city\"> {{city.lon}} </td>\n  </ng-container>\n\n  <!-- CountryName Column -->\n  <ng-container matColumnDef=\"countryName\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Country</th>\n    <td mat-cell *matCellDef=\"let city\">\n      <a [routerLink]=\"['/country', city.countryId]\">{{city.countryName}}</a>\n    </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!cities\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/cities/cities.component.spec.ts",
    "content": "import { async, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { RouterTestingModule } from '@angular/router/testing';\nimport { AngularMaterialModule } from '../angular-material.module';\nimport { of } from 'rxjs';\n\nimport { CitiesComponent } from './cities.component';\nimport { City } from './city';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\ndescribe('CitiesComponent', () => {\n  let fixture: ComponentFixture<CitiesComponent>;\n  let component: CitiesComponent;\n\n  // async beforeEach(): TestBed initialization\n  beforeEach(async(() => {\n\n    // Create a mock cityService object with a mock 'getData' method\n    let cityService = jasmine.createSpyObj<CityService>(\n      'CityService', ['getData']\n    );\n\n    // Configure the 'getData' spy method\n    cityService.getData.and.returnValue(\n      // return an Observable with some test data\n      of<ApiResult<City>>(<ApiResult<City>>{\n        data: [\n          <City>{\n            name: 'TestCity1',\n            id: 1, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          },\n          <City>{\n            name: 'TestCity2',\n            id: 2, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          },\n          <City>{\n            name: 'TestCity3',\n            id: 3, lat: 1, lon: 1,\n            countryId: 1, countryName: 'TestCountry1'\n          }\n        ],\n        totalCount: 3,\n        pageIndex: 0,\n        pageSize: 10\n      }));\n\n    TestBed.configureTestingModule({\n      declarations: [CitiesComponent],\n      imports: [\n        BrowserAnimationsModule,\n        AngularMaterialModule,\n        RouterTestingModule\n      ],\n      providers: [\n        {\n          provide: CityService,\n          useValue: cityService\n        }\n      ]\n    })\n      .compileComponents();\n  }));\n\n  // synchronous beforeEach(): fixtures and components setup\n  beforeEach(() => {\n    fixture = TestBed.createComponent(CitiesComponent);\n    component = fixture.componentInstance;\n\n    component.paginator = jasmine.createSpyObj(\n      \"MatPaginator\", [\"length\", \"pageIndex\", \"pageSize\"]\n    );\n\n    fixture.detectChanges();\n  });\n\n  it('should display a \"Cities\" title', async(() => {\n    let title = fixture.nativeElement\n      .querySelector('h1');\n    expect(title.textContent).toEqual('Cities');\n  }));\n\n  it('should contain a table with a list of one or more cities', async(() => {\n    let table = fixture.nativeElement\n      .querySelector('table.mat-table');\n    let tableRows = table\n      .querySelectorAll('tr.mat-row');\n    expect(tableRows.length).toBeGreaterThan(0);\n  }));\n});\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/cities/cities.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { City } from './city';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-cities',\n  templateUrl: './cities.component.html',\n  styleUrls: ['./cities.component.css']\n})\nexport class CitiesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'lat', 'lon', 'countryName'];\n  public cities: MatTableDataSource<City>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private cityService: CityService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n    var sortColumn = (this.sort)\n      ? this.sort.active\n      : this.defaultSortColumn;\n\n    var sortOrder = (this.sort)\n      ? this.sort.direction\n      : this.defaultSortOrder;\n\n    var filterColumn = (this.filterQuery)\n      ? this.defaultFilterColumn\n      : null;\n\n    var filterQuery = (this.filterQuery)\n      ? this.filterQuery\n      : null;\n\n    this.cityService.getData<ApiResult<City>>(\n      event.pageIndex,\n      event.pageSize,\n      sortColumn,\n      sortOrder,\n      filterColumn,\n      filterQuery)\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.cities = new MatTableDataSource<City>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/cities/city-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/cities/city-edit.component.html",
    "content": "<div class=\"city-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !city\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n\n        <div *ngIf=\"form.invalid && form.errors?.isDupeCity\"\n             class=\"alert alert-danger\">\n              <strong>ERROR</strong>:\n              A city with the same <i>name</i>, <i>lat</i>,\n              <i>lon</i> and <i>country</i> already exists.\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"name\">City name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"City name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"hasError('name')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"lat\">City latitude:</label>\n            <br />\n            <input type=\"text\" id=\"lat\"\n                   formControlName=\"lat\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lat')\"\n                  class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lat').errors?.required\">\n                  Latitude is required.\n                </div>\n                <div *ngIf=\"form.get('lat').errors?.pattern\">\n                  Latitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n\n\n        <div class=\"form-group\">\n            <label for=\"lon\">City longitude:</label>\n            <br />\n            <input type=\"text\" id=\"lon\"\n                   formControlName=\"lon\" required\n                   placeholder=\"Latitude...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"hasError('lon')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('lon').errors?.required\">\n                  Longitude is required.\n                </div>\n                <div *ngIf=\"form.get('lon').errors?.pattern\">\n                  Longitude requires a positive or negative number with 0-4 decimal values.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group\">\n            <label for=\"countryId\">Country:</label>\n            <br />\n            <mat-form-field *ngIf=\"countries\">\n              <mat-label>Select a Country...</mat-label>\n              <mat-select id=\"countryId\" formControlName=\"countryId\">\n                <mat-option *ngFor=\"let country of countries\" [value]=\"country.id\">\n                  {{country.name}}\n                </mat-option>\n              </mat-select>\n            </mat-form-field>\n\n            <div *ngIf=\"hasError('countryId')\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('countryId').errors?.required\">\n                  Please select a Country.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/cities']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n\n<!-- Form debug info panel -->\n<div class=\"card bg-light mb-3\">\n  <div class=\"card-header\">Form Debug Info</div>\n  <div class=\"card-body\">\n    <div class=\"card-text\">\n      <div><strong>Form value:</strong></div>\n      <div class=\"help-block\">\n          {{ form.value | json }}\n      </div>\n      <div class=\"mt-2\"><strong>Form status:</strong></div>\n      <div class=\"help-block\">\n          {{ form.status | json }}\n      </div>\n    </div>\n  </div>\n</div>\n\n<!-- Form activity log panel -->\n<div class=\"card bg-light mb-3\">\n  <div class=\"card-header\">Form Activity Log</div>\n  <div class=\"card-body\">\n    <div class=\"card-text\">\n      <div class=\"help-block\">\n        <span *ngIf=\"activityLog\" \n            [innerHTML]=\"activityLog\"></span>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/cities/city-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormControl, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { City } from './city';\nimport { Country } from '../countries/country';\nimport { CityService } from './city.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-city-edit',\n  templateUrl: './city-edit.component.html',\n  styleUrls: ['./city-edit.component.css']\n})\nexport class CityEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  city: City;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new city,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  // the countries array for the select\n  countries: Country[];\n\n  // Activity Log (for debugging purposes)\n  activityLog: string = '';\n\n  constructor(\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private cityService: CityService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = new FormGroup({\n      name: new FormControl('', Validators.required),\n      lat: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      lon: new FormControl('', [\n        Validators.required,\n        Validators.pattern(/^[-]?[0-9]+(\\.[0-9]{1,4})?$/)\n      ]),\n      countryId: new FormControl('', Validators.required)\n    }, null, this.isDupeCity());\n\n    // react to form changes\n    this.form.valueChanges\n      .subscribe(val => {\n        if (!this.form.dirty) {\n          this.log(\"Form Model has been loaded.\");\n        }\n        else {\n          this.log(\"Form was updated by the user.\");\n        }\n      });\n\n    // react to changes in the form.name control\n    this.form.get(\"name\")!.valueChanges\n      .subscribe(val => {\n        if (!this.form.dirty) {\n          this.log(\"Name has been loaded with initial values.\");\n        }\n        else {\n          this.log(\"Name was updated by the user.\");\n        }\n      });\n\n    this.loadData();\n  }\n\n  log(str: string) {\n    this.activityLog += \"[\"\n      + new Date().toLocaleString()\n      + \"] \" + str + \"<br />\";\n  }\n\n  loadData() {\n\n    // load countries\n    this.loadCountries();\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the city from the server\n      this.cityService.get<City>(this.id).subscribe(result => {\n        this.city = result;\n        this.title = \"Edit - \" + this.city.name;\n\n        // update the form with the city value\n        this.form.patchValue(this.city);\n      }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new City\";\n    }\n  }\n\n  loadCountries() {\n    // fetch all the countries from the server\n    this.cityService.getCountries<ApiResult<Country>>(\n      0,\n      9999,\n      \"name\",\n      null,\n      null,\n      null,\n    ).subscribe(result => {\n      this.countries = result.data;\n    }, error => console.error(error));\n  }\n\n  onSubmit() {\n\n    var city = (this.id) ? this.city : <City>{};\n\n    city.name = this.form.get(\"name\").value;\n    city.lat = +this.form.get(\"lat\").value;\n    city.lon = +this.form.get(\"lon\").value;\n    city.countryId = +this.form.get(\"countryId\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.cityService\n        .put<City>(city)\n        .subscribe(result => {\n\n          console.log(\"City \" + city.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.cityService\n        .post<City>(city)\n        .subscribe(result => {\n\n          console.log(\"City \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/cities']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeCity(): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n      var city = <City>{};\n      city.id = (this.id) ? this.id : 0;\n      city.name = this.form.get(\"name\").value;\n      city.lat = +this.form.get(\"lat\").value;\n      city.lon = +this.form.get(\"lon\").value;\n      city.countryId = +this.form.get(\"countryId\").value;\n\n      return this.cityService.isDupeCity(city)\n        .pipe(map(result => {\n          return (result ? { isDupeCity: true } : null);\n        }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/cities/city.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CityService\n    extends BaseService {\n    constructor(\n        http: HttpClient,\n        @Inject('BASE_URL') baseUrl: string) {\n        super(http, baseUrl);\n    }\n\n    getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Cities';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    get<City>(id): Observable<City> {\n        var url = this.baseUrl + \"api/Cities/\" + id;\n        return this.http.get<City>(url);\n    }\n\n    put<City>(item): Observable<City> {\n        var url = this.baseUrl + \"api/Cities/\" + item.id;\n        return this.http.put<City>(url, item);\n    }\n\n    post<City>(item): Observable<City> {\n        var url = this.baseUrl + \"api/Cities\";\n        return this.http.post<City>(url, item);\n    }\n\n    getCountries<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Countries';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    isDupeCity(item): Observable<boolean> {\n        var url = this.baseUrl + \"api/Cities/IsDupeCity\";\n        return this.http.post<boolean>(url, item);\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/cities/city.ts",
    "content": "export interface City {\n    id: number;\n    name: string;\n    lat: number;\n    lon: number;\n    countryId: number;\n    countryName: string;\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/countries/countries.component.css",
    "content": "table {\n  width: 100%;\n}\n\n.mat-form-field {\n  font-size: 14px;\n  width: 100%;\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/countries/countries.component.html",
    "content": "<h1>Countries</h1>\n\n<p>Here's a list of countries: feel free to play with it.</p>\n\n<p *ngIf=\"!countries\"><em>Loading...</em></p>\n\n<div class=\"commands text-right\" *ngIf=\"countries\">\n  <button type=\"button\"\n          [routerLink]=\"['/country']\"\n          class=\"btn btn-success\">\n      Add a new Country\n  </button>\n</div>\n\n<mat-form-field [hidden]=\"!countries\">\n  <input matInput (keyup)=\"loadData($event.target.value)\"\n      placeholder=\"Filter by name (or part of it)...\">\n</mat-form-field>\n\n<table mat-table [dataSource]=\"countries\" class=\"mat-elevation-z8\" [hidden]=\"!countries\"\n    matSort (matSortChange)=\"loadData()\"\n    matSortActive=\"{{defaultSortColumn}}\" matSortDirection=\"{{defaultSortOrder}}\">\n\n  <!-- Id Column -->\n  <ng-container matColumnDef=\"id\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.id}} </td>\n  </ng-container>\n\n  <!-- Name Column -->\n  <ng-container matColumnDef=\"name\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>\n    <td mat-cell *matCellDef=\"let country\">\n      <a [routerLink]=\"['/country', country.id]\">{{country.name}}</a>\n    </td>\n  </ng-container>\n\n  <!-- Lat Column -->\n  <ng-container matColumnDef=\"iso2\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 2</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso2}} </td>\n  </ng-container>\n\n  <!-- Lon Column -->\n  <ng-container matColumnDef=\"iso3\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>ISO 3</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.iso3}} </td>\n  </ng-container>\n\n  <!-- TotCities Column -->\n  <ng-container matColumnDef=\"totCities\">\n    <th mat-header-cell *matHeaderCellDef mat-sort-header>Tot. Cities</th>\n    <td mat-cell *matCellDef=\"let country\"> {{country.totCities}} </td>\n  </ng-container>\n\n  <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n  <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n﻿\n<!-- Pagination directive -->\n<mat-paginator [hidden]=\"!countries\"\n    (page)=\"getData($event)\"\n    [pageSize]=\"10\"\n    [pageSizeOptions]=\"[10, 20, 50]\"\n    showFirstLastButtons></mat-paginator>\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/countries/countries.component.ts",
    "content": "import { Component, Inject, ViewChild } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { MatTableDataSource } from '@angular/material/table';\nimport { MatPaginator, PageEvent } from '@angular/material/paginator';\nimport { MatSort } from '@angular/material/sort';\n\nimport { Country } from './country';\nimport { CountryService } from './country.service';\nimport { ApiResult } from '../base.service';\n\n@Component({\n  selector: 'app-countries',\n  templateUrl: './countries.component.html',\n  styleUrls: ['./countries.component.css']\n})\nexport class CountriesComponent {\n  public displayedColumns: string[] = ['id', 'name', 'iso2', 'iso3', 'totCities'];\n  public countries: MatTableDataSource<Country>;\n\n  defaultPageIndex: number = 0;\n  defaultPageSize: number = 10;\n  public defaultSortColumn: string = \"name\";\n  public defaultSortOrder: string = \"asc\";\n\n  defaultFilterColumn: string = \"name\";\n  filterQuery: string = null;\n\n  @ViewChild(MatPaginator) paginator: MatPaginator;\n  @ViewChild(MatSort) sort: MatSort;\n\n  constructor(\n    private countryService: CountryService) {\n  }\n\n  ngOnInit() {\n    this.loadData(null);\n  }\n\n  loadData(query: string = null) {\n    var pageEvent = new PageEvent();\n    pageEvent.pageIndex = this.defaultPageIndex;\n    pageEvent.pageSize = this.defaultPageSize;\n    if (query) {\n      this.filterQuery = query;\n    }\n    this.getData(pageEvent);\n  }\n\n  getData(event: PageEvent) {\n\n    var sortColumn = (this.sort)\n      ? this.sort.active\n      : this.defaultSortColumn;\n\n    var sortOrder = (this.sort)\n      ? this.sort.direction\n      : this.defaultSortOrder;\n\n    var filterColumn = (this.filterQuery)\n      ? this.defaultFilterColumn\n      : null;\n\n    var filterQuery = (this.filterQuery)\n      ? this.filterQuery\n      : null;\n\n    this.countryService.getData<ApiResult<Country>>(\n      event.pageIndex,\n      event.pageSize,\n      sortColumn,\n      sortOrder,\n      filterColumn,\n      filterQuery)\n      .subscribe(result => {\n        this.paginator.length = result.totalCount;\n        this.paginator.pageIndex = result.pageIndex;\n        this.paginator.pageSize = result.pageSize;\n        this.countries = new MatTableDataSource<Country>(result.data);\n      }, error => console.error(error));\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/countries/country-edit.component.css",
    "content": "input.ng-valid {\n  border-left: 5px solid green;\n}\n\ninput.ng-invalid.ng-dirty,\ninput.ng-invalid.ng-touched {\n  border-left: 5px solid red;\n}\n\ninput.ng-valid ~ .valid-feedback,\ninput.ng-invalid ~ .invalid-feedback {\n  display: block;\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/countries/country-edit.component.html",
    "content": "<div class=\"country-edit\">\n    <h1>{{title}}</h1>\n\n    <p *ngIf=\"this.id && !country\"><em>Loading...</em></p>\n\n    <div class=\"form\" [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n     \n        <div class=\"form-group\">\n            <label for=\"name\">Country name:</label>\n            <br />\n            <input type=\"text\" id=\"name\"\n                formControlName=\"name\" required\n                placeholder=\"Country name...\"\n                class=\"form-control\"\n                />\n\n            <div *ngIf=\"form.get('name').invalid &&\n                 (form.get('name').dirty || form.get('name').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('name').errors?.required\">\n                  Name is required.\n                </div>\n                <div *ngIf=\"form.get('name').errors?.isDupeField\">\n                  Name does already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n        <div class=\"form-group\">\n            <label for=\"iso2\">ISO 3166-1 ALPHA-2 Country Code (2 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso2\"\n                   formControlName=\"iso2\" required\n                   placeholder=\"2 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso2').invalid &&\n                 (form.get('iso2').dirty || form.get('iso2').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso2').errors?.required\">\n                  ISO 3166-1 ALPHA-2 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.pattern\">\n                  ISO 3166-1 ALPHA-2 country code requires 2 letters.\n                </div>\n                <div *ngIf=\"form.get('iso2').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-2 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n\n              <div class=\"form-group\">\n            <label for=\"iso3\">ISO 3166-1 ALPHA-3 Country Code (3 letters)</label>\n            <br />\n            <input type=\"text\" id=\"iso3\"\n                   formControlName=\"iso3\" required\n                   placeholder=\"3 letters country code...\"\n                   class=\"form-control\" />\n\n            <div *ngIf=\"form.get('iso3').invalid &&\n                 (form.get('iso3').dirty || form.get('iso3').touched)\"\n                 class=\"invalid-feedback\">\n                <div *ngIf=\"form.get('iso3').errors?.required\">\n                  ISO 3166-1 ALPHA-3 country code is required.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.pattern\">\n                  ISO 3166-1 ALPHA-3 country code requires 3 letters.\n                </div>\n                <div *ngIf=\"form.get('iso3').errors?.isDupeField\">\n                  This ISO 3166-1 ALPHA-3 country code already exist: please choose another.\n                </div>\n            </div>\n        </div>\n      \n        <div class=\"form-group commands\">\n            <button *ngIf=\"id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Save\n            </button>\n            <button *ngIf=\"!id\" type=\"submit\"\n                    (click)=\"onSubmit()\"\n                    [disabled]=\"form.invalid\"\n                    class=\"btn btn-success\">\n                Create\n            </button>\n            <button type=\"button\"\n                    [routerLink]=\"['/countries']\"\n                    class=\"btn\">\n                Cancel\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/countries/country-edit.component.ts",
    "content": "import { Component, Inject } from '@angular/core';\n// import { HttpClient, HttpParams } from '@angular/common/http';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { FormGroup, FormBuilder, Validators, AbstractControl, AsyncValidatorFn } from '@angular/forms';\nimport { map } from 'rxjs/operators';\nimport { Observable } from 'rxjs';\nimport { BaseFormComponent } from '../base.form.component';\n\nimport { Country } from '../countries/country';\nimport { CountryService } from './country.service';\n\n@Component({\n  selector: 'app-country-edit',\n  templateUrl: './country-edit.component.html',\n  styleUrls: ['./country-edit.component.css']\n})\nexport class CountryEditComponent\n  extends BaseFormComponent {\n\n  // the view title\n  title: string;\n\n  // the form model\n  form: FormGroup;\n\n  // the city object to edit or create\n  country: Country;\n\n  // the city object id, as fetched from the active route:\n  // It's NULL when we're adding a new country,\n  // and not NULL when we're editing an existing one.\n  id?: number;\n\n  constructor(\n    private fb: FormBuilder,\n    private activatedRoute: ActivatedRoute,\n    private router: Router,\n    private countryService: CountryService) {\n    super();\n  }\n\n  ngOnInit() {\n    this.form = this.fb.group({\n      name: ['',\n        Validators.required,\n        this.isDupeField(\"name\")\n      ],\n      iso2: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{2}/)\n        ],\n        this.isDupeField(\"iso2\")\n      ],\n      iso3: ['',\n        [\n          Validators.required,\n          Validators.pattern(/[a-zA-Z]{3}/)\n        ],\n        this.isDupeField(\"iso3\")\n      ]\n    });\n\n    this.loadData();\n  }\n\n  loadData() {\n\n    // retrieve the ID from the 'id'\n    this.id = +this.activatedRoute.snapshot.paramMap.get('id');\n    if (this.id) {\n      // EDIT MODE\n\n      // fetch the country from the server\n      this.countryService.get<Country>(this.id)\n        .subscribe(result => {\n          this.country = result;\n          this.title = \"Edit - \" + this.country.name;\n\n          // update the form with the country value\n          this.form.patchValue(this.country);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW MODE\n\n      this.title = \"Create a new Country\";\n    }\n  }\n\n  onSubmit() {\n\n    var country = (this.id) ? this.country : <Country>{};\n\n    country.name = this.form.get(\"name\").value;\n    country.iso2 = this.form.get(\"iso2\").value;\n    country.iso3 = this.form.get(\"iso3\").value;\n\n    if (this.id) {\n      // EDIT mode\n      this.countryService\n        .put<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + country.id + \" has been updated.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n    else {\n      // ADD NEW mode\n      this.countryService\n        .post<Country>(country)\n        .subscribe(result => {\n\n          console.log(\"Country \" + result.id + \" has been created.\");\n\n          // go back to cities view\n          this.router.navigate(['/countries']);\n        }, error => console.error(error));\n    }\n  }\n\n  isDupeField(fieldName: string): AsyncValidatorFn {\n    return (control: AbstractControl): Observable<{ [key: string]: any } | null> => {\n\n      var countryId = (this.id) ? this.id.toString() : \"0\";\n\n      return this.countryService.isDupeField(\n        countryId,\n        fieldName,\n        control.value)\n        .pipe(map(result => {\n          return (result ? { isDupeField: true } : null);\n        }));\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/countries/country.service.ts",
    "content": "import { Injectable, Inject } from '@angular/core';\nimport { HttpClient, HttpParams } from '@angular/common/http';\nimport { BaseService, ApiResult } from '../base.service';\nimport { Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class CountryService\n    extends BaseService {\n    constructor(\n        http: HttpClient,\n        @Inject('BASE_URL') baseUrl: string) {\n        super(http, baseUrl);\n    }\n\n    getData<ApiResult>(\n        pageIndex: number,\n        pageSize: number,\n        sortColumn: string,\n        sortOrder: string,\n        filterColumn: string,\n        filterQuery: string\n    ): Observable<ApiResult> {\n        var url = this.baseUrl + 'api/Countries';\n        var params = new HttpParams()\n            .set(\"pageIndex\", pageIndex.toString())\n            .set(\"pageSize\", pageSize.toString())\n            .set(\"sortColumn\", sortColumn)\n            .set(\"sortOrder\", sortOrder);\n\n        if (filterQuery) {\n            params = params\n                .set(\"filterColumn\", filterColumn)\n                .set(\"filterQuery\", filterQuery);\n        }\n\n        return this.http.get<ApiResult>(url, { params });\n    }\n\n    get<Country>(id): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries/\" + id;\n        return this.http.get<Country>(url);\n    }\n\n    put<Country>(item): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries/\" + item.id;\n        return this.http.put<Country>(url, item);\n    }\n\n    post<Country>(item): Observable<Country> {\n        var url = this.baseUrl + \"api/Countries\";\n        return this.http.post<Country>(url, item);\n    }\n\n    isDupeField(countryId, fieldName, fieldValue): Observable<boolean> {\n        var params = new HttpParams()\n            .set(\"countryId\", countryId)\n            .set(\"fieldName\", fieldName)\n            .set(\"fieldValue\", fieldValue);\n        var url = this.baseUrl + \"api/Countries/IsDupeField\";\n        return this.http.post<boolean>(url, null, { params });\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/countries/country.ts",
    "content": "export interface Country {\n    id: number;\n    name: string;\n    iso2: string;\n    iso3: string;\n    totCities: number;\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/home/home.component.html",
    "content": "<h1>Hello, world!</h1>\n<p>Welcome to your new single-page application, built with:</p>\n<ul>\n  <li><a href='https://get.asp.net/'>ASP.NET Core</a> and <a href='https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx'>C#</a> for cross-platform server-side code</li>\n  <li><a href='https://angular.io/'>Angular</a> and <a href='http://www.typescriptlang.org/'>TypeScript</a> for client-side code</li>\n  <li><a href='http://getbootstrap.com/'>Bootstrap</a> for layout and styling</li>\n</ul>\n<p>To help you get started, we've also set up:</p>\n<ul>\n  <li><strong>Client-side navigation</strong>. For example, click <em>Counter</em> then <em>Back</em> to return here.</li>\n  <li><strong>Angular CLI integration</strong>. In development mode, there's no need to run <code>ng serve</code>. It runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.</li>\n  <li><strong>Efficient production builds</strong>. In production mode, development-time features are disabled, and your <code>dotnet publish</code> configuration automatically invokes <code>ng build</code> to produce minified, ahead-of-time compiled JavaScript files.</li>\n</ul>\n<p>The <code>ClientApp</code> subdirectory is a standard Angular CLI application. If you open a command prompt in that directory, you can run any <code>ng</code> command (e.g., <code>ng test</code>), or use <code>npm</code> to install extra packages into it.</p>\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/home/home.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-home',\n  templateUrl: './home.component.html',\n})\nexport class HomeComponent {\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.css",
    "content": "a.navbar-brand {\n  white-space: normal;\n  text-align: center;\n  word-break: break-all;\n}\n\nhtml {\n  font-size: 14px;\n}\n@media (min-width: 768px) {\n  html {\n    font-size: 16px;\n  }\n}\n\n.box-shadow {\n  box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.html",
    "content": "<header>\n  <nav\n    class=\"navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3\"\n  >\n    <div class=\"container\">\n      <a class=\"navbar-brand\" [routerLink]=\"['/']\">WorldCities</a>\n      <button\n        class=\"navbar-toggler\"\n        type=\"button\"\n        data-toggle=\"collapse\"\n        data-target=\".navbar-collapse\"\n        aria-label=\"Toggle navigation\"\n        [attr.aria-expanded]=\"isExpanded\"\n        (click)=\"toggle()\"\n      >\n        <span class=\"navbar-toggler-icon\"></span>\n      </button>\n      <div\n        class=\"navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse\"\n        [ngClass]=\"{ show: isExpanded }\"\n      >\n        <app-login-menu></app-login-menu>\n        <ul class=\"navbar-nav flex-grow\">\n          <li\n            class=\"nav-item\"\n            [routerLinkActive]=\"['link-active']\"\n            [routerLinkActiveOptions]=\"{ exact: true }\"\n          >\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/']\">Home</a>\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/cities']\"\n              >Cities</a\n            >\n          </li>\n          <li class=\"nav-item\" [routerLinkActive]=\"['link-active']\">\n            <a class=\"nav-link text-dark\" [routerLink]=\"['/countries']\"\n              >Countries</a\n            >\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</header>\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/app/nav-menu/nav-menu.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'app-nav-menu',\n  templateUrl: './nav-menu.component.html',\n  styleUrls: ['./nav-menu.component.css']\n})\nexport class NavMenuComponent {\n  isExpanded = false;\n\n  collapse() {\n    this.isExpanded = false;\n  }\n\n  toggle() {\n    this.isExpanded = !this.isExpanded;\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true\n};\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/environments/environment.ts",
    "content": "// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n  production: false\n};\n\n/*\n * In development mode, to ignore zone related error stack frames such as\n * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can\n * import the following file, but please comment it out in production mode\n * because it will have performance impact when throw error\n */\n// import 'zone.js/dist/zone-error';  // Included with Angular CLI.\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\"/>\n    <title>WorldCities</title>\n    <base href=\"/\"/>\n\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\"/>\n      <link href=\"https://fonts.googleapis.com/css?family=Roboto:300,400,500&amp;display=swap\" rel=\"stylesheet\">\n    <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n  <link rel=\"manifest\" href=\"manifest.webmanifest\">\n  <meta name=\"theme-color\" content=\"#1976d2\">\n</head>\n  <body>\n    <app-root>Loading...</app-root>\n    <noscript>Please enable JavaScript to continue using this application.</noscript>\n</body>\n</html>\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage-istanbul-reporter'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    coverageIstanbulReporter: {\n      dir: require('path').join(__dirname, '../coverage'),\n      reports: ['html', 'lcovonly'],\n      fixWebpackSourcePaths: true\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false\n  });\n};\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/main.ts",
    "content": "import 'hammerjs';\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nexport function getBaseUrl() {\n  return document.getElementsByTagName('base')[0].href;\n}\n\nconst providers = [\n  { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] }\n];\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic(providers).bootstrapModule(AppModule)\n  .catch(err => console.error(err));\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/manifest.webmanifest",
    "content": "{\n  \"name\": \"WorldCities\",\n  \"short_name\": \"WorldCities\",\n  \"theme_color\": \"#1976d2\",\n  \"background_color\": \"#fafafa\",\n  \"display\": \"standalone\",\n  \"scope\": \"/\",\n  \"start_url\": \"/\",\n  \"icons\": [\n    {\n      \"src\": \"assets/icons/icon-72x72.png\",\n      \"sizes\": \"72x72\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-96x96.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-128x128.png\",\n      \"sizes\": \"128x128\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-144x144.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-152x152.png\",\n      \"sizes\": \"152x152\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-384x384.png\",\n      \"sizes\": \"384x384\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ]\n}"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/ng-connection-service/connection-service.module.ts",
    "content": "import {NgModule} from '@angular/core';\nimport {ConnectionService} from './connection-service.service';\nimport {HttpClientModule} from '@angular/common/http';\n\n@NgModule({\n  imports: [HttpClientModule],\n  providers: [ConnectionService]\n})\nexport class ConnectionServiceModule {\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/ng-connection-service/connection-service.service.spec.ts",
    "content": "import { TestBed, inject } from '@angular/core/testing';\n\nimport { ConnectionService } from './connection-service.service';\n\ndescribe('ConnectionServiceService', () => {\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      providers: [ConnectionService]\n    });\n  });\n\n  it('should be created', inject([ConnectionService], (service: ConnectionService) => {\n    expect(service).toBeTruthy();\n  }));\n});\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/ng-connection-service/connection-service.service.ts",
    "content": "import {EventEmitter, Inject, Injectable, InjectionToken, OnDestroy, Optional} from '@angular/core';\nimport {fromEvent, Observable, Subscription, timer} from 'rxjs';\nimport {debounceTime, delay, retryWhen, startWith, switchMap, tap} from 'rxjs/operators';\nimport {HttpClient} from '@angular/common/http';\nimport * as _ from 'lodash';\n\n/**\n * Instance of this interface is used to report current connection status.\n */\nexport interface ConnectionState {\n  /**\n   * \"True\" if browser has network connection. Determined by Window objects \"online\" / \"offline\" events.\n   */\n  hasNetworkConnection: boolean;\n  /**\n   * \"True\" if browser has Internet access. Determined by heartbeat system which periodically makes request to heartbeat Url.\n   */\n  hasInternetAccess: boolean;\n}\n\n/**\n * Instance of this interface could be used to configure \"ConnectionService\".\n */\nexport interface ConnectionServiceOptions {\n  /**\n   * Controls the Internet connectivity heartbeat system. Default value is 'true'.\n   */\n  enableHeartbeat?: boolean;\n  /**\n   * Url used for checking Internet connectivity, heartbeat system periodically makes \"HEAD\" requests to this URL to determine Internet\n   * connection status. Default value is \"//internethealthtest.org\".\n   */\n  heartbeatUrl?: string;\n  /**\n   * Interval used to check Internet connectivity specified in milliseconds. Default value is \"30000\".\n   */\n  heartbeatInterval?: number;\n  /**\n   * Interval used to retry Internet connectivity checks when an error is detected (when no Internet connection). Default value is \"1000\".\n   */\n  heartbeatRetryInterval?: number;\n  /**\n   * HTTP method used for requesting heartbeat Url. Default is 'head'.\n   */\n  requestMethod?: 'get' | 'post' | 'head' | 'options';\n\n}\n\n/**\n * InjectionToken for specifing ConnectionService options.\n */\nexport const ConnectionServiceOptionsToken: InjectionToken<ConnectionServiceOptions> = new InjectionToken('ConnectionServiceOptionsToken');\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class ConnectionService implements OnDestroy {\n  private static DEFAULT_OPTIONS: ConnectionServiceOptions = {\n    enableHeartbeat: true,\n    heartbeatUrl: '//internethealthtest.org',\n    heartbeatInterval: 30000,\n    heartbeatRetryInterval: 1000,\n    requestMethod: 'head'\n  };\n\n  private stateChangeEventEmitter = new EventEmitter<ConnectionState>();\n\n  private currentState: ConnectionState = {\n    hasInternetAccess: false,\n    hasNetworkConnection: window.navigator.onLine\n  };\n  private offlineSubscription: Subscription;\n  private onlineSubscription: Subscription;\n  private httpSubscription: Subscription;\n  private serviceOptions: ConnectionServiceOptions;\n\n  /**\n   * Current ConnectionService options. Notice that changing values of the returned object has not effect on service execution.\n   * You should use \"updateOptions\" function.\n   */\n  get options(): ConnectionServiceOptions {\n    return _.clone(this.serviceOptions);\n  }\n\n  constructor(private http: HttpClient, @Inject(ConnectionServiceOptionsToken) @Optional() options: ConnectionServiceOptions) {\n    this.serviceOptions = _.defaults({}, options, ConnectionService.DEFAULT_OPTIONS);\n\n    this.checkNetworkState();\n    this.checkInternetState();\n  }\n\n  private checkInternetState() {\n\n    if (!_.isNil(this.httpSubscription)) {\n      this.httpSubscription.unsubscribe();\n    }\n\n    if (this.serviceOptions.enableHeartbeat) {\n      this.httpSubscription = timer(0, this.serviceOptions.heartbeatInterval)\n        .pipe(\n          switchMap(() => this.http[this.serviceOptions.requestMethod](this.serviceOptions.heartbeatUrl, {responseType: 'text'})),\n          retryWhen(errors =>\n            errors.pipe(\n              // log error message\n              tap(val => {\n                console.error('Http error:', val);\n                this.currentState.hasInternetAccess = false;\n                this.emitEvent();\n              }),\n              // restart after 5 seconds\n              delay(this.serviceOptions.heartbeatRetryInterval)\n            )\n          )\n        )\n        .subscribe(result => {\n          this.currentState.hasInternetAccess = true;\n          this.emitEvent();\n        });\n    } else {\n      this.currentState.hasInternetAccess = false;\n      this.emitEvent();\n    }\n  }\n\n  private checkNetworkState() {\n    this.onlineSubscription = fromEvent(window, 'online').subscribe(() => {\n      this.currentState.hasNetworkConnection = true;\n      this.checkInternetState();\n      this.emitEvent();\n    });\n\n    this.offlineSubscription = fromEvent(window, 'offline').subscribe(() => {\n      this.currentState.hasNetworkConnection = false;\n      this.checkInternetState();\n      this.emitEvent();\n    });\n  }\n\n  private emitEvent() {\n    this.stateChangeEventEmitter.emit(this.currentState);\n  }\n\n  ngOnDestroy(): void {\n    try {\n      this.offlineSubscription.unsubscribe();\n      this.onlineSubscription.unsubscribe();\n      this.httpSubscription.unsubscribe();\n    } catch (e) {\n    }\n  }\n\n  /**\n   * Monitor Network & Internet connection status by subscribing to this observer. If you set \"reportCurrentState\" to \"false\" then\n   * function will not report current status of the connections when initially subscribed.\n   * @param reportCurrentState Report current state when initial subscription. Default is \"true\"\n   */\n  monitor(reportCurrentState = true): Observable<ConnectionState> {\n    return reportCurrentState ?\n      this.stateChangeEventEmitter.pipe(\n        debounceTime(300),\n        startWith(this.currentState),\n      )\n      :\n      this.stateChangeEventEmitter.pipe(\n        debounceTime(300)\n      );\n  }\n\n  /**\n   * Update options of the service. You could specify partial options object. Values that are not specified will use default / previous\n   * option values.\n   * @param options Partial option values.\n   */\n  updateOptions(options: Partial<ConnectionServiceOptions>) {\n    this.serviceOptions = _.defaults({}, options, this.serviceOptions);\n    this.checkInternetState();\n  }\n\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),\n * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/** IE10 and IE11 requires the following for NgClass support on SVG elements */\n// import 'classlist.js';  // Run `npm install --save classlist.js`.\n\n/**\n * Web Animations `@angular/platform-browser/animations`\n * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.\n * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).\n */\n// import 'web-animations-js';  // Run `npm install --save web-animations-js`.\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags.ts';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js/dist/zone';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/styles.css",
    "content": "/* You can add global styles to this file, and also import other style files */\n\n/* Provide sufficient contrast against white background */\na {\n  color: #0366d6;\n}\n\ncode {\n  color: #e01a76;\n}\n\n.btn-primary {\n  color: #fff;\n  background-color: #1b6ec2;\n  border-color: #1861ac;\n}\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/dist/zone-testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: any;\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting()\n);\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().map(context);\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/tsconfig.app.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/app\",\n    \"types\": []\n  },\n  \"exclude\": [\n    \"src/test.ts\",\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/tsconfig.server.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"commonjs\"\n  },\n  \"angularCompilerOptions\": {\n    \"entryModule\": \"app/app.server.module#AppServerModule\"\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/tsconfig.spec.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\",\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"test.ts\",\n    \"polyfills.ts\"\n  ],\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/src/tslint.json",
    "content": "{\n    \"extends\": \"../tslint.json\",\n    \"rules\": {\n        \"directive-selector\": [\n            true,\n            \"attribute\",\n            \"app\",\n            \"camelCase\"\n        ],\n        \"component-selector\": [\n            true,\n            \"element\",\n            \"app\",\n            \"kebab-case\"\n        ]\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/tsconfig.json",
    "content": "{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"module\": \"esnext\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"moduleResolution\": \"node\",\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es2015\",\n    \"typeRoots\": [\n      \"node_modules/@types\"\n    ],\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ]\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/tslint.json",
    "content": "{\n  \"rulesDirectory\": [\n    \"node_modules/codelyzer\"\n  ],\n  \"rules\": {\n    \"arrow-return-shorthand\": true,\n    \"callable-types\": true,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"deprecation\": {\n      \"severity\": \"warn\"\n    },\n    \"eofline\": true,\n    \"forin\": true,\n    \"import-blacklist\": [\n      true,\n      \"rxjs/Rx\"\n    ],\n    \"import-spacing\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-over-type-literal\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      {\n        \"order\": [\n          \"static-field\",\n          \"instance-field\",\n          \"static-method\",\n          \"instance-method\"\n        ]\n      }\n    ],\n    \"no-arg\": true,\n    \"no-bitwise\": true,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-debugger\": true,\n    \"no-duplicate-super\": true,\n    \"no-empty\": false,\n    \"no-empty-interface\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": [\n      true,\n      \"ignore-params\"\n    ],\n    \"no-misused-new\": true,\n    \"no-non-null-assertion\": true,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": false,\n    \"no-string-throw\": true,\n    \"no-switch-case-fall-through\": true,\n    \"no-trailing-whitespace\": true,\n    \"no-unnecessary-initializer\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"prefer-const\": true,\n    \"quotemark\": [\n      true,\n      \"single\"\n    ],\n    \"radix\": true,\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"unified-signatures\": true,\n    \"variable-name\": false,\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-output-on-prefix\": true,\n    \"no-inputs-metadata-property\": true,\n    \"no-outputs-metadata-property\": true,\n    \"no-host-metadata-property\": true,\n    \"no-input-rename\": true,\n    \"no-output-rename\": true,\n    \"use-lifecycle-interface\": true,\n    \"use-pipe-transform-interface\": true,\n    \"component-class-suffix\": true,\n    \"directive-class-suffix\": true\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/ClientApp/update-npm.bat",
    "content": "cd %~dp0\nnpm update\n"
  },
  {
    "path": "Chapter_12/WorldCities/Controllers/CitiesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CitiesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CitiesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Cities/?pageIndex=0&pageSize=10\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Cities/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CityDTO>>> GetCities(\n                int pageIndex = 0,\n                int pageSize = 10,\n                string sortColumn = null,\n                string sortOrder = null,\n                string filterColumn = null,\n                string filterQuery = null)\n        {\n            return await ApiResult<CityDTO>.CreateAsync(\n                    _context.Cities\n                        .Select(c => new CityDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            Lat = c.Lat,\n                            Lon = c.Lon,\n                            CountryId = c.Country.Id,\n                            CountryName = c.Country.Name\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        // GET: api/Cities/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<City>> GetCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            return city;\n        }\n\n        // PUT: api/Cities/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCity(int id, City city)\n        {\n            if (id != city.Id)\n            {\n                return BadRequest();\n            }\n\n            //var sourceCity = _context.Cities.Where(i => i.Id == city.Id).FirstOrDefault();\n            //if (sourceCity == null) return BadRequest();\n            //sourceCity.Name = city.Name;\n            //sourceCity.Lat = city.Lat;\n            //sourceCity.Lon = city.Lon;\n\n            _context.Entry(city).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CityExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Cities\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPost]\n        public async Task<ActionResult<City>> PostCity(City city)\n        {\n            _context.Cities.Add(city);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCity\", new { id = city.Id }, city);\n        }\n\n        // DELETE: api/Cities/5\n        [Authorize]\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<City>> DeleteCity(int id)\n        {\n            var city = await _context.Cities.FindAsync(id);\n            if (city == null)\n            {\n                return NotFound();\n            }\n\n            _context.Cities.Remove(city);\n            await _context.SaveChangesAsync();\n\n            return city;\n        }\n\n        private bool CityExists(int id)\n        {\n            return _context.Cities.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeCity\")]\n        public bool IsDupeCity(City city)\n        {\n            return _context.Cities.Any(\n                e => e.Name == city.Name\n                && e.Lat == city.Lat\n                && e.Lon == city.Lon\n                && e.CountryId == city.CountryId\n                && e.Id != city.Id);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Controllers/CountriesController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Linq.Dynamic.Core;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.EntityFrameworkCore;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]\")]\n    [ApiController]\n    public class CountriesController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n\n        public CountriesController(ApplicationDbContext context)\n        {\n            _context = context;\n        }\n\n        // GET: api/Cities\n        // GET: api/Countries/?pageIndex=0&pageSize=10\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc\n        // GET: api/Countries/?pageIndex=0&pageSize=10&sortColumn=name&sortOrder=asc&filterColumn=name&filterQuery=york\n        [HttpGet]\n        public async Task<ActionResult<ApiResult<CountryDTO>>> GetCountries(\n        int pageIndex = 0,\n        int pageSize = 10,\n        string sortColumn = null,\n        string sortOrder = null,\n        string filterColumn = null,\n        string filterQuery = null)\n        {\n            return await ApiResult<CountryDTO>.CreateAsync(\n                    _context.Countries\n                        .Select(c => new CountryDTO()\n                        {\n                            Id = c.Id,\n                            Name = c.Name,\n                            ISO2 = c.ISO2,\n                            ISO3 = c.ISO3,\n                            TotCities = c.Cities.Count\n                        }),\n                    pageIndex,\n                    pageSize,\n                    sortColumn,\n                    sortOrder,\n                    filterColumn,\n                    filterQuery);\n        }\n\n        //public async Task<ActionResult<ApiResult<dynamic>>> GetCountries(\n        //    int pageIndex = 0,\n        //    int pageSize = 10,\n        //    string sortColumn = null,\n        //    string sortOrder = null,\n        //    string filterColumn = null,\n        //    string filterQuery = null)\n        //{\n        //    return await ApiResult<dynamic>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n\n        //public async Task<ActionResult<ApiResult<Country>>> GetCountries(\n        //int pageIndex = 0,\n        //int pageSize = 10,\n        //string sortColumn = null,\n        //string sortOrder = null,\n        //string filterColumn = null,\n        //string filterQuery = null)\n        //{\n        //    return await ApiResult<Country>.CreateAsync(\n        //            _context.Countries\n        //                .Select(c => new Country()\n        //                {\n        //                    Id = c.Id,\n        //                    Name = c.Name,\n        //                    ISO2 = c.ISO2,\n        //                    ISO3 = c.ISO3,\n        //                    TotCities = c.Cities.Count\n        //                }),\n        //            pageIndex,\n        //            pageSize,\n        //            sortColumn,\n        //            sortOrder,\n        //            filterColumn,\n        //            filterQuery);\n        //}\n\n        // GET: api/Countries/5\n        [HttpGet(\"{id}\")]\n        public async Task<ActionResult<Country>> GetCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            return country;\n        }\n\n        // PUT: api/Countries/5\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPut(\"{id}\")]\n        public async Task<IActionResult> PutCountry(int id, Country country)\n        {\n            if (id != country.Id)\n            {\n                return BadRequest();\n            }\n\n            _context.Entry(country).State = EntityState.Modified;\n\n            try\n            {\n                await _context.SaveChangesAsync();\n            }\n            catch (DbUpdateConcurrencyException)\n            {\n                if (!CountryExists(id))\n                {\n                    return NotFound();\n                }\n                else\n                {\n                    throw;\n                }\n            }\n\n            return NoContent();\n        }\n\n        // POST: api/Countries\n        // To protect from overposting attacks, please enable the specific properties you want to bind to, for\n        // more details see https://aka.ms/RazorPagesCRUD.\n        [Authorize]\n        [HttpPost]\n        public async Task<ActionResult<Country>> PostCountry(Country country)\n        {\n            _context.Countries.Add(country);\n            await _context.SaveChangesAsync();\n\n            return CreatedAtAction(\"GetCountry\", new { id = country.Id }, country);\n        }\n\n        // DELETE: api/Countries/5\n        [Authorize]\n        [HttpDelete(\"{id}\")]\n        public async Task<ActionResult<Country>> DeleteCountry(int id)\n        {\n            var country = await _context.Countries.FindAsync(id);\n            if (country == null)\n            {\n                return NotFound();\n            }\n\n            _context.Countries.Remove(country);\n            await _context.SaveChangesAsync();\n\n            return country;\n        }\n\n        private bool CountryExists(int id)\n        {\n            return _context.Countries.Any(e => e.Id == id);\n        }\n\n        [HttpPost]\n        [Route(\"IsDupeField\")]\n        public bool IsDupeField(\n            int countryId, \n            string fieldName, \n            string fieldValue)\n        {\n            // Standard approach(using strongly-typed LAMBA expressions)\n            //switch (fieldName)\n            //{\n            //    case \"name\":\n            //        return _context.Countries.Any(\n            //            c => c.Name == fieldValue && c.Id != countryId);\n            //    case \"iso2\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO2 == fieldValue && c.Id != countryId);\n            //    case \"iso3\":\n            //        return _context.Countries.Any(\n            //            c => c.ISO3 == fieldValue && c.Id != countryId);\n            //    default:\n            //        return false;\n            //}\n\n            // Dynamic approach (using System.Linq.Dynamic.Core)\n            return (ApiResult<Country>.IsValidProperty(fieldName, true))\n                ? _context.Countries.Any(\n                    String.Format(\"{0} == @0 && Id != @1\", fieldName),\n                    fieldValue,\n                    countryId)\n                : false;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Controllers/OidcConfigurationController.cs",
    "content": "﻿using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Controllers\n{\n    public class OidcConfigurationController : Controller\n    {\n        private readonly ILogger<OidcConfigurationController> logger;\n\n        public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger<OidcConfigurationController> _logger)\n        {\n            ClientRequestParametersProvider = clientRequestParametersProvider;\n            logger = _logger;\n        }\n\n        public IClientRequestParametersProvider ClientRequestParametersProvider { get; }\n\n        [HttpGet(\"_configuration/{clientId}\")]\n        public IActionResult GetClientRequestParameters([FromRoute]string clientId)\n        {\n            var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId);\n            return Ok(parameters);\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Controllers/SeedController.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing WorldCities.Data;\nusing OfficeOpenXml;\nusing System.IO;\nusing Microsoft.AspNetCore.Hosting;\nusing WorldCities.Data.Models;\nusing System.Text.Json;\nusing Microsoft.AspNetCore.Identity;\n\nnamespace WorldCities.Controllers\n{\n    [Route(\"api/[controller]/[action]\")]\n    [ApiController]\n    public class SeedController : ControllerBase\n    {\n        private readonly ApplicationDbContext _context;\n        private readonly RoleManager<IdentityRole> _roleManager;\n        private readonly UserManager<ApplicationUser> _userManager;\n        private readonly IWebHostEnvironment _env;\n\n        public SeedController(\n            ApplicationDbContext context,\n            RoleManager<IdentityRole> roleManager,\n            UserManager<ApplicationUser> userManager,\n            IWebHostEnvironment env)\n        {\n            _context = context;\n            _roleManager = roleManager;\n            _userManager = userManager;\n            _env = env;\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> Import()\n        {\n            // NOTE: This method has been updated on 2020.09.13.\n            // The new version is more efficient than the code described in the book's Chapter 4.\n            // ref.: https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/issues/15\n\n            var path = Path.Combine(\n                _env.ContentRootPath,\n                String.Format(\"Data/Source/worldcities.xlsx\"));\n\n            using (var stream = new FileStream(\n                path,\n                FileMode.Open,\n                FileAccess.Read))\n            {\n                using (var ep = new ExcelPackage(stream))\n                {\n                    // get the first worksheet\n\n                    var ws = ep.Workbook.Worksheets[0];\n\n                    // initialize the record counters\n                    var nCountries = 0;\n                    var nCities = 0;\n\n                    #region Import all Countries\n                    // create a list containing all the countries already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCountries = _context.Countries.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n                        var name = row[nRow, 5].GetValue<string>();\n\n                        // does this country already exist?\n                        if (lstCountries.Where(c => c.Name == name).Count() == 0)\n                        {\n                            // create the Country entity and fill it with xlsx data\n                            var country = new Country();\n                            country.Name = name;\n                            country.ISO2 = row[nRow, 6].GetValue<string>();\n                            country.ISO3 = row[nRow, 7].GetValue<string>();\n\n                            // add the new country to the DB context\n                            _context.Countries.Add(country);\n\n                            // store the country to retrieve its Id later on\n                            lstCountries.Add(country);\n\n                            // increment the counter\n                            nCountries++;\n                        }\n                    }\n\n                    // save all the countries into the Database\n                    if (nCountries > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    #region Import all Cities\n                    // create a list containing all the cities already existing\n                    // into the Database (it will be empty on first run).\n                    var lstCities = _context.Cities.ToList();\n\n                    // iterates through all rows, skipping the first one\n                    for (int nRow = 2;\n                        nRow <= ws.Dimension.End.Row;\n                        nRow++)\n                    {\n                        var row = ws.Cells[nRow, 1, nRow, ws.Dimension.End.Column];\n\n                        var name = row[nRow, 1].GetValue<string>();\n                        var name_ASCII = row[nRow, 2].GetValue<string>();\n                        var countryName = row[nRow, 5].GetValue<string>();\n                        var lat = row[nRow, 3].GetValue<decimal>();\n                        var lon = row[nRow, 4].GetValue<decimal>();\n                        // retrieve country and countryId\n                        var country = lstCountries.Where(c => c.Name == countryName)\n                            .FirstOrDefault();\n                        var countryId = country.Id;\n\n                        // does this city already exist?\n                        if (lstCities.Where(\n                            c => c.Name == name\n                            && c.Lat == lat\n                            && c.Lon == lon\n                            && c.CountryId == countryId\n                        ).Count() == 0)\n                        {\n                            // create the City entity and fill it with xlsx data\n                            var city = new City();\n                            city.Name = name;\n                            city.Name_ASCII = name_ASCII;\n                            city.Lat = lat;\n                            city.Lon = lon;\n                            city.CountryId = countryId;\n\n                            // add the new city to the DB context\n                            _context.Cities.Add(city);\n\n                            // increment the counter\n                            nCities++;\n                        }\n                    }\n\n                    // save all the cities into the Database\n                    if (nCities > 0) await _context.SaveChangesAsync();\n                    #endregion\n\n                    return new JsonResult(new\n                    {\n                        Cities = nCities,\n                        Countries = nCountries\n                    });\n                }\n            }\n        }\n\n        [HttpGet]\n        public async Task<ActionResult> CreateDefaultUsers()\n        {\n            // setup the default role names\n            string role_RegisteredUser = \"RegisteredUser\";\n            string role_Administrator = \"Administrator\";\n\n            // create the default roles (if they doesn't exist yet)\n            if (await _roleManager.FindByNameAsync(role_RegisteredUser) == null)\n                await _roleManager.CreateAsync(new IdentityRole(role_RegisteredUser));\n\n            if (await _roleManager.FindByNameAsync(role_Administrator) == null)\n                await _roleManager.CreateAsync(new IdentityRole(role_Administrator));\n\n            // create a list to track the newly added users\n            var addedUserList = new List<ApplicationUser>();\n\n            // check if the admin user already exist\n            var email_Admin = \"admin@email.com\";\n            if (await _userManager.FindByNameAsync(email_Admin) == null)\n            {\n                // create a new admin ApplicationUser account\n                var user_Admin = new ApplicationUser()\n                {\n                    SecurityStamp = Guid.NewGuid().ToString(),\n                    UserName = email_Admin,\n                    Email = email_Admin,\n                };\n\n                // insert the admin user into the DB\n                await _userManager.CreateAsync(user_Admin, \"MySecr3t$\");\n\n                // assign the \"RegisteredUser\" and \"Administrator\" roles\n                await _userManager.AddToRoleAsync(user_Admin, role_RegisteredUser);\n                await _userManager.AddToRoleAsync(user_Admin, role_Administrator);\n\n                // confirm the e-mail and remove lockout\n                user_Admin.EmailConfirmed = true;\n                user_Admin.LockoutEnabled = false;\n\n                // add the admin user to the added users list\n                addedUserList.Add(user_Admin);\n            }\n\n            // check if the standard user already exist\n            var email_User = \"user@email.com\";\n            if (await _userManager.FindByNameAsync(email_User) == null)\n            {\n                // create a new standard ApplicationUser account\n                var user_User = new ApplicationUser()\n                {\n                    SecurityStamp = Guid.NewGuid().ToString(),\n                    UserName = email_User,\n                    Email = email_User\n                };\n\n                // insert the standard user into the DB\n                await _userManager.CreateAsync(user_User, \"MySecr3t$\");\n\n                // assign the \"RegisteredUser\" role\n                await _userManager.AddToRoleAsync(user_User, role_RegisteredUser);\n\n                // confirm the e-mail and remove lockout\n                user_User.EmailConfirmed = true;\n                user_User.LockoutEnabled = false;\n\n                // add the standard user to the added users list\n                addedUserList.Add(user_User);\n            }\n\n            // if we added at least one user, persist the changes into the DB\n            if (addedUserList.Count > 0)\n                await _context.SaveChangesAsync();\n\n            return new JsonResult(new\n            {\n                Count = addedUserList.Count,\n                Users = addedUserList\n            });\n        }\n    }\n}"
  },
  {
    "path": "Chapter_12/WorldCities/Data/ApiResult.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Linq.Dynamic.Core;\nusing System.Reflection;\nusing System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class ApiResult<T>\n    {\n        /// <summary>\n        /// Private constructor called by the CreateAsync method.\n        /// </summary>\n        private ApiResult(\n            List<T> data,\n            int count,\n            int pageIndex,\n            int pageSize,\n            string sortColumn,\n            string sortOrder,\n            string filterColumn,\n            string filterQuery)\n        {\n            Data = data;\n            PageIndex = pageIndex;\n            PageSize = pageSize;\n            TotalCount = count;\n            TotalPages = (int)Math.Ceiling(count / (double)pageSize);\n            SortColumn = sortColumn;\n            SortOrder = sortOrder;\n            FilterColumn = filterColumn;\n            FilterQuery = filterQuery;\n        }\n\n        #region Methods\n        /// <summary>\n        /// Pages, sorts and/or filters a IQueryable source.\n        /// </summary>\n        /// <param name=\"source\">An IQueryable source of generic type</param>\n        /// <param name=\"pageIndex\">Zero-based current page index (0 = first page)</param>\n        /// <param name=\"pageSize\">The actual size of each page</param>\n        /// <param name=\"sortColumn\">The sorting colum name</param>\n        /// <param name=\"sortOrder\">The sorting order (\"ASC\" or \"DESC\")</param>\n        /// <param name=\"filterColumn\">The filtering column name</param>\n        /// <param name=\"filterQuery\">The filtering query (value to lookup)</param>\n        /// <returns>\n        /// A object containing the IQueryable paged/sorted/filtered result \n        /// and all the relevant paging/sorting/filtering navigation info.\n        /// </returns>\n        public static async Task<ApiResult<T>> CreateAsync(\n            IQueryable<T> source,\n            int pageIndex,\n            int pageSize,\n            string sortColumn = null,\n            string sortOrder = null,\n            string filterColumn = null,\n            string filterQuery = null)\n        {\n            if (!String.IsNullOrEmpty(filterColumn)\n                && !String.IsNullOrEmpty(filterQuery)\n                && IsValidProperty(filterColumn))\n            {\n                source = source.Where(\n                    String.Format(\"{0}.Contains(@0)\",\n                    filterColumn),\n                    filterQuery);\n            }\n\n            var count = await source.CountAsync();\n\n            if (!String.IsNullOrEmpty(sortColumn)\n                && IsValidProperty(sortColumn))\n            {\n                sortOrder = !String.IsNullOrEmpty(sortOrder)\n                    && sortOrder.ToUpper() == \"ASC\"\n                    ? \"ASC\"\n                    : \"DESC\";\n                source = source.OrderBy(\n                    String.Format(\n                        \"{0} {1}\",\n                        sortColumn,\n                        sortOrder)\n                    );\n            }\n\n            source = source\n                .Skip(pageIndex * pageSize)\n                .Take(pageSize);\n\n            // retrieve the SQL query (for debug purposes)\n            #if DEBUG\n            {\n                var sql = source.ToSql();\n                // do something with the sql string\n            }\n            #endif\n\n            var data = await source.ToListAsync();\n\n            return new ApiResult<T>(\n                data,\n                count,\n                pageIndex,\n                pageSize,\n                sortColumn,\n                sortOrder,\n                filterColumn,\n                filterQuery);\n        }\n\n        /// <summary>\n        /// Checks if the given property name exists\n        /// to protect against SQL injection attacks\n        /// </summary>\n        public static bool IsValidProperty(\n            string propertyName,\n            bool throwExceptionIfNotFound = true)\n        {\n            var prop = typeof(T).GetProperty(\n                propertyName,\n                BindingFlags.IgnoreCase |\n                BindingFlags.Public |\n                BindingFlags.Static |\n                BindingFlags.Instance);\n            if (prop == null && throwExceptionIfNotFound)\n                throw new NotSupportedException(\n                    String.Format(\n                        \"ERROR: Property '{0}' does not exist.\",\n                        propertyName)\n                    );\n            return prop != null;\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The data result.\n        /// </summary>\n        public List<T> Data { get; private set; }\n\n        /// <summary>\n        /// Zero-based index of current page.\n        /// </summary>\n        public int PageIndex { get; private set; }\n\n        /// <summary>\n        /// Number of items contained in each page.\n        /// </summary>\n        public int PageSize { get; private set; }\n\n        /// <summary>\n        /// Total items count\n        /// </summary>\n        public int TotalCount { get; private set; }\n\n        /// <summary>\n        /// Total pages count\n        /// </summary>\n        public int TotalPages { get; private set; }\n\n        /// <summary>\n        /// TRUE if the current page has a previous page, FALSE otherwise.\n        /// </summary>\n        public bool HasPreviousPage\n        {\n            get\n            {\n                return (PageIndex > 0);\n            }\n        }\n\n        /// <summary>\n        /// TRUE if the current page has a next page, FALSE otherwise.\n        /// </summary>\n        public bool HasNextPage\n        {\n            get\n            {\n                return ((PageIndex +1) < TotalPages);\n            }\n        }\n\n        /// <summary>\n        /// Sorting Column name (or null if none set)\n        /// </summary>\n        public string SortColumn { get; set; }\n\n        /// <summary>\n        /// Sorting Order (\"ASC\", \"DESC\" or null if none set)\n        /// </summary>\n        public string SortOrder { get; set; }\n\n        /// <summary>\n        /// Filter Column name (or null if none set)\n        /// </summary>\n        public string FilterColumn { get; set; }\n\n        /// <summary>\n        /// Filter Query string \n        /// (to be used within the given FilterColumn)\n        /// </summary>\n        public string FilterQuery { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Data/ApplicationDbContext.cs",
    "content": "﻿using IdentityServer4.EntityFramework.Options;\nusing Microsoft.AspNetCore.ApiAuthorization.IdentityServer;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Options;\nusing WorldCities.Data.Models;\n\nnamespace WorldCities.Data\n{\n    public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>\n    {\n        #region Constructor\n        public ApplicationDbContext(\n            DbContextOptions options,\n            IOptions<OperationalStoreOptions> operationalStoreOptions) \n            : base(options, operationalStoreOptions)\n        {\n        }\n        #endregion Constructor\n\n        #region Methods\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            base.OnModelCreating(modelBuilder);\n\n            // Map Entity names to DB Table names\n            modelBuilder.Entity<City>().ToTable(\"Cities\");\n            modelBuilder.Entity<Country>().ToTable(\"Countries\");\n        }\n        #endregion Methods\n\n        #region Properties\n        public DbSet<City> Cities { get; set; }\n        public DbSet<Country> Countries { get; set; }\n        #endregion Properties\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Data/CityDTO.cs",
    "content": "﻿namespace WorldCities.Data\n{\n    public class CityDTO\n    {\n        public CityDTO() { }\n\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        public string Name_ASCII { get; set; }\n\n        public decimal Lat { get; set; }\n\n        public decimal Lon { get; set; }\n\n        public int CountryId { get; set; }\n\n        public string CountryName { get; set; }\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Data/CountryDTO.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace WorldCities.Data\n{\n    public class CountryDTO\n    {\n        public CountryDTO() { }\n\n        #region Properties\n        public int Id { get; set; }\n\n        public string Name { get; set; }\n\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n\n        public int TotCities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Data/IQueryableExtensions.cs",
    "content": "﻿using Microsoft.EntityFrameworkCore.Query;\nusing Microsoft.EntityFrameworkCore.Query.SqlExpressions;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Reflection;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data\n{\n    public static class IQueryableExtension\n    {\n        public static string ToSql<T>(this IQueryable<T> query)\n        {\n            var enumerator = query.Provider\n                .Execute<IEnumerable<T>>(query.Expression).GetEnumerator();\n            var relationalCommandCache = enumerator\n                .Private(\"_relationalCommandCache\");\n            var selectExpression = relationalCommandCache\n                .Private<SelectExpression>(\"_selectExpression\");\n            var factory = relationalCommandCache\n                .Private<IQuerySqlGeneratorFactory>(\"_querySqlGeneratorFactory\");\n\n            var sqlGenerator = factory.Create();\n            var command = sqlGenerator.GetCommand(selectExpression);\n\n            string sql = command.CommandText;\n            return sql;\n        }\n\n        private static object Private(this object obj, string privateField) => \n            obj?.GetType()\n            .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?\n            .GetValue(obj);\n        private static T Private<T>(this object obj, string privateField) => \n            (T)obj?\n            .GetType()\n            .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?\n            .GetValue(obj);\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Data/Migrations/20191230002753_Identity.Designer.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Migrations;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    [Migration(\"20191230002753_Identity\")]\n    partial class Identity\n    {\n        protected override void BuildTargetModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.1.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.DeviceFlowCodes\", b =>\n                {\n                    b.Property<string>(\"UserCode\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<string>(\"DeviceCode\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .IsRequired()\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.HasKey(\"UserCode\");\n\n                    b.HasIndex(\"DeviceCode\")\n                        .IsUnique();\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.ToTable(\"DeviceCodes\");\n                });\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.PersistedGrant\", b =>\n                {\n                    b.Property<string>(\"Key\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(50)\")\n                        .HasMaxLength(50);\n\n                    b.HasKey(\"Key\");\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.HasIndex(\"SubjectId\", \"ClientId\", \"Type\");\n\n                    b.ToTable(\"PersistedGrants\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRole\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedName\")\n                        .IsUnique()\n                        .HasName(\"RoleNameIndex\")\n                        .HasFilter(\"[NormalizedName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetRoleClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderKey\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderDisplayName\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"LoginProvider\", \"ProviderKey\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserLogins\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"UserId\", \"RoleId\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetUserRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Value\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"UserId\", \"LoginProvider\", \"Name\");\n\n                    b.ToTable(\"AspNetUserTokens\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.ApplicationUser\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<int>(\"AccessFailedCount\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Email\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<bool>(\"EmailConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<bool>(\"LockoutEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<DateTimeOffset?>(\"LockoutEnd\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.Property<string>(\"NormalizedEmail\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedUserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"PasswordHash\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"PhoneNumber\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"PhoneNumberConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"SecurityStamp\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"TwoFactorEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"UserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedEmail\")\n                        .HasName(\"EmailIndex\");\n\n                    b.HasIndex(\"NormalizedUserName\")\n                        .IsUnique()\n                        .HasName(\"UserNameIndex\")\n                        .HasFilter(\"[NormalizedUserName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetUsers\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Data/Migrations/20191230002753_Identity.cs",
    "content": "﻿using System;\nusing Microsoft.EntityFrameworkCore.Migrations;\n\nnamespace WorldCities.Data.Migrations\n{\n    public partial class Identity : Migration\n    {\n        protected override void Up(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.CreateTable(\n                name: \"AspNetRoles\",\n                columns: table => new\n                {\n                    Id = table.Column<string>(nullable: false),\n                    Name = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedName = table.Column<string>(maxLength: 256, nullable: true),\n                    ConcurrencyStamp = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetRoles\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUsers\",\n                columns: table => new\n                {\n                    Id = table.Column<string>(nullable: false),\n                    UserName = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),\n                    Email = table.Column<string>(maxLength: 256, nullable: true),\n                    NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),\n                    EmailConfirmed = table.Column<bool>(nullable: false),\n                    PasswordHash = table.Column<string>(nullable: true),\n                    SecurityStamp = table.Column<string>(nullable: true),\n                    ConcurrencyStamp = table.Column<string>(nullable: true),\n                    PhoneNumber = table.Column<string>(nullable: true),\n                    PhoneNumberConfirmed = table.Column<bool>(nullable: false),\n                    TwoFactorEnabled = table.Column<bool>(nullable: false),\n                    LockoutEnd = table.Column<DateTimeOffset>(nullable: true),\n                    LockoutEnabled = table.Column<bool>(nullable: false),\n                    AccessFailedCount = table.Column<int>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUsers\", x => x.Id);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"DeviceCodes\",\n                columns: table => new\n                {\n                    UserCode = table.Column<string>(maxLength: 200, nullable: false),\n                    DeviceCode = table.Column<string>(maxLength: 200, nullable: false),\n                    SubjectId = table.Column<string>(maxLength: 200, nullable: true),\n                    ClientId = table.Column<string>(maxLength: 200, nullable: false),\n                    CreationTime = table.Column<DateTime>(nullable: false),\n                    Expiration = table.Column<DateTime>(nullable: false),\n                    Data = table.Column<string>(maxLength: 50000, nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_DeviceCodes\", x => x.UserCode);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"PersistedGrants\",\n                columns: table => new\n                {\n                    Key = table.Column<string>(maxLength: 200, nullable: false),\n                    Type = table.Column<string>(maxLength: 50, nullable: false),\n                    SubjectId = table.Column<string>(maxLength: 200, nullable: true),\n                    ClientId = table.Column<string>(maxLength: 200, nullable: false),\n                    CreationTime = table.Column<DateTime>(nullable: false),\n                    Expiration = table.Column<DateTime>(nullable: true),\n                    Data = table.Column<string>(maxLength: 50000, nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_PersistedGrants\", x => x.Key);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetRoleClaims\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    RoleId = table.Column<string>(nullable: false),\n                    ClaimType = table.Column<string>(nullable: true),\n                    ClaimValue = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetRoleClaims\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_AspNetRoleClaims_AspNetRoles_RoleId\",\n                        column: x => x.RoleId,\n                        principalTable: \"AspNetRoles\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserClaims\",\n                columns: table => new\n                {\n                    Id = table.Column<int>(nullable: false)\n                        .Annotation(\"SqlServer:Identity\", \"1, 1\"),\n                    UserId = table.Column<string>(nullable: false),\n                    ClaimType = table.Column<string>(nullable: true),\n                    ClaimValue = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserClaims\", x => x.Id);\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserClaims_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserLogins\",\n                columns: table => new\n                {\n                    LoginProvider = table.Column<string>(maxLength: 128, nullable: false),\n                    ProviderKey = table.Column<string>(maxLength: 128, nullable: false),\n                    ProviderDisplayName = table.Column<string>(nullable: true),\n                    UserId = table.Column<string>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserLogins\", x => new { x.LoginProvider, x.ProviderKey });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserLogins_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserRoles\",\n                columns: table => new\n                {\n                    UserId = table.Column<string>(nullable: false),\n                    RoleId = table.Column<string>(nullable: false)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserRoles\", x => new { x.UserId, x.RoleId });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserRoles_AspNetRoles_RoleId\",\n                        column: x => x.RoleId,\n                        principalTable: \"AspNetRoles\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserRoles_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateTable(\n                name: \"AspNetUserTokens\",\n                columns: table => new\n                {\n                    UserId = table.Column<string>(nullable: false),\n                    LoginProvider = table.Column<string>(maxLength: 128, nullable: false),\n                    Name = table.Column<string>(maxLength: 128, nullable: false),\n                    Value = table.Column<string>(nullable: true)\n                },\n                constraints: table =>\n                {\n                    table.PrimaryKey(\"PK_AspNetUserTokens\", x => new { x.UserId, x.LoginProvider, x.Name });\n                    table.ForeignKey(\n                        name: \"FK_AspNetUserTokens_AspNetUsers_UserId\",\n                        column: x => x.UserId,\n                        principalTable: \"AspNetUsers\",\n                        principalColumn: \"Id\",\n                        onDelete: ReferentialAction.Cascade);\n                });\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetRoleClaims_RoleId\",\n                table: \"AspNetRoleClaims\",\n                column: \"RoleId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"RoleNameIndex\",\n                table: \"AspNetRoles\",\n                column: \"NormalizedName\",\n                unique: true,\n                filter: \"[NormalizedName] IS NOT NULL\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserClaims_UserId\",\n                table: \"AspNetUserClaims\",\n                column: \"UserId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserLogins_UserId\",\n                table: \"AspNetUserLogins\",\n                column: \"UserId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_AspNetUserRoles_RoleId\",\n                table: \"AspNetUserRoles\",\n                column: \"RoleId\");\n\n            migrationBuilder.CreateIndex(\n                name: \"EmailIndex\",\n                table: \"AspNetUsers\",\n                column: \"NormalizedEmail\");\n\n            migrationBuilder.CreateIndex(\n                name: \"UserNameIndex\",\n                table: \"AspNetUsers\",\n                column: \"NormalizedUserName\",\n                unique: true,\n                filter: \"[NormalizedUserName] IS NOT NULL\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_DeviceCodes_DeviceCode\",\n                table: \"DeviceCodes\",\n                column: \"DeviceCode\",\n                unique: true);\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_DeviceCodes_Expiration\",\n                table: \"DeviceCodes\",\n                column: \"Expiration\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_PersistedGrants_Expiration\",\n                table: \"PersistedGrants\",\n                column: \"Expiration\");\n\n            migrationBuilder.CreateIndex(\n                name: \"IX_PersistedGrants_SubjectId_ClientId_Type\",\n                table: \"PersistedGrants\",\n                columns: new[] { \"SubjectId\", \"ClientId\", \"Type\" });\n        }\n\n        protected override void Down(MigrationBuilder migrationBuilder)\n        {\n            migrationBuilder.DropTable(\n                name: \"AspNetRoleClaims\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserClaims\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserLogins\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserRoles\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUserTokens\");\n\n            migrationBuilder.DropTable(\n                name: \"DeviceCodes\");\n\n            migrationBuilder.DropTable(\n                name: \"PersistedGrants\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetRoles\");\n\n            migrationBuilder.DropTable(\n                name: \"AspNetUsers\");\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Data/Migrations/ApplicationDbContextModelSnapshot.cs",
    "content": "﻿// <auto-generated />\nusing System;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore.Infrastructure;\nusing Microsoft.EntityFrameworkCore.Metadata;\nusing Microsoft.EntityFrameworkCore.Storage.ValueConversion;\nusing WorldCities.Data;\n\nnamespace WorldCities.Data.Migrations\n{\n    [DbContext(typeof(ApplicationDbContext))]\n    partial class ApplicationDbContextModelSnapshot : ModelSnapshot\n    {\n        protected override void BuildModel(ModelBuilder modelBuilder)\n        {\n#pragma warning disable 612, 618\n            modelBuilder\n                .HasAnnotation(\"ProductVersion\", \"3.1.0\")\n                .HasAnnotation(\"Relational:MaxIdentifierLength\", 128)\n                .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.DeviceFlowCodes\", b =>\n                {\n                    b.Property<string>(\"UserCode\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<string>(\"DeviceCode\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .IsRequired()\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.HasKey(\"UserCode\");\n\n                    b.HasIndex(\"DeviceCode\")\n                        .IsUnique();\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.ToTable(\"DeviceCodes\");\n                });\n\n            modelBuilder.Entity(\"IdentityServer4.EntityFramework.Entities.PersistedGrant\", b =>\n                {\n                    b.Property<string>(\"Key\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"ClientId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<DateTime>(\"CreationTime\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"Data\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(max)\")\n                        .HasMaxLength(50000);\n\n                    b.Property<DateTime?>(\"Expiration\")\n                        .HasColumnType(\"datetime2\");\n\n                    b.Property<string>(\"SubjectId\")\n                        .HasColumnType(\"nvarchar(200)\")\n                        .HasMaxLength(200);\n\n                    b.Property<string>(\"Type\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(50)\")\n                        .HasMaxLength(50);\n\n                    b.HasKey(\"Key\");\n\n                    b.HasIndex(\"Expiration\");\n\n                    b.HasIndex(\"SubjectId\", \"ClientId\", \"Type\");\n\n                    b.ToTable(\"PersistedGrants\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRole\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedName\")\n                        .IsUnique()\n                        .HasName(\"RoleNameIndex\")\n                        .HasFilter(\"[NormalizedName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetRoleClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ClaimType\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ClaimValue\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserClaims\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderKey\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"ProviderDisplayName\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"UserId\")\n                        .IsRequired()\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"LoginProvider\", \"ProviderKey\");\n\n                    b.HasIndex(\"UserId\");\n\n                    b.ToTable(\"AspNetUserLogins\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"RoleId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.HasKey(\"UserId\", \"RoleId\");\n\n                    b.HasIndex(\"RoleId\");\n\n                    b.ToTable(\"AspNetUserRoles\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.Property<string>(\"UserId\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<string>(\"LoginProvider\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(128)\")\n                        .HasMaxLength(128);\n\n                    b.Property<string>(\"Value\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"UserId\", \"LoginProvider\", \"Name\");\n\n                    b.ToTable(\"AspNetUserTokens\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.ApplicationUser\", b =>\n                {\n                    b.Property<string>(\"Id\")\n                        .HasColumnType(\"nvarchar(450)\");\n\n                    b.Property<int>(\"AccessFailedCount\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<string>(\"ConcurrencyStamp\")\n                        .IsConcurrencyToken()\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Email\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<bool>(\"EmailConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<bool>(\"LockoutEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<DateTimeOffset?>(\"LockoutEnd\")\n                        .HasColumnType(\"datetimeoffset\");\n\n                    b.Property<string>(\"NormalizedEmail\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"NormalizedUserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.Property<string>(\"PasswordHash\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"PhoneNumber\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"PhoneNumberConfirmed\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"SecurityStamp\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<bool>(\"TwoFactorEnabled\")\n                        .HasColumnType(\"bit\");\n\n                    b.Property<string>(\"UserName\")\n                        .HasColumnType(\"nvarchar(256)\")\n                        .HasMaxLength(256);\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"NormalizedEmail\")\n                        .HasName(\"EmailIndex\");\n\n                    b.HasIndex(\"NormalizedUserName\")\n                        .IsUnique()\n                        .HasName(\"UserNameIndex\")\n                        .HasFilter(\"[NormalizedUserName] IS NOT NULL\");\n\n                    b.ToTable(\"AspNetUsers\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<int>(\"CountryId\")\n                        .HasColumnType(\"int\");\n\n                    b.Property<decimal>(\"Lat\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<decimal>(\"Lon\")\n                        .HasColumnType(\"decimal(7,4)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name_ASCII\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.HasIndex(\"CountryId\");\n\n                    b.ToTable(\"Cities\");\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.Country\", b =>\n                {\n                    b.Property<int>(\"Id\")\n                        .ValueGeneratedOnAdd()\n                        .HasColumnType(\"int\")\n                        .HasAnnotation(\"SqlServer:ValueGenerationStrategy\", SqlServerValueGenerationStrategy.IdentityColumn);\n\n                    b.Property<string>(\"ISO2\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"ISO3\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.Property<string>(\"Name\")\n                        .HasColumnType(\"nvarchar(max)\");\n\n                    b.HasKey(\"Id\");\n\n                    b.ToTable(\"Countries\");\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserClaim<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserLogin<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserRole<string>\", b =>\n                {\n                    b.HasOne(\"Microsoft.AspNetCore.Identity.IdentityRole\", null)\n                        .WithMany()\n                        .HasForeignKey(\"RoleId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"Microsoft.AspNetCore.Identity.IdentityUserToken<string>\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.ApplicationUser\", null)\n                        .WithMany()\n                        .HasForeignKey(\"UserId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n\n            modelBuilder.Entity(\"WorldCities.Data.Models.City\", b =>\n                {\n                    b.HasOne(\"WorldCities.Data.Models.Country\", \"Country\")\n                        .WithMany(\"Cities\")\n                        .HasForeignKey(\"CountryId\")\n                        .OnDelete(DeleteBehavior.Cascade)\n                        .IsRequired();\n                });\n#pragma warning restore 612, 618\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Data/Models/ApplicationUser.cs",
    "content": "﻿using Microsoft.AspNetCore.Identity;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class ApplicationUser : IdentityUser\n    {\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Data/Models/City.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class City\n    {\n        #region Constructor\n        public City()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this City\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// City name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// City name (in ASCII format)\n        /// </summary>\n        public string Name_ASCII { get; set; }\n\n        /// <summary>\n        /// City latitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lat { get; set; }\n\n        /// <summary>\n        /// City longitude\n        /// </summary>\n        [Column(TypeName = \"decimal(7,4)\")]\n        public decimal Lon { get; set; }\n\n        /// <summary>\n        /// Country Id (foreign key)\n        /// </summary>\n        [ForeignKey(\"Country\")]\n        public int CountryId { get; set; }\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// The country related to this city.\n        /// </summary>\n        public virtual Country Country { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Data/Models/Country.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.ComponentModel.DataAnnotations.Schema;\nusing System.Linq;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\n\nnamespace WorldCities.Data.Models\n{\n    public class Country\n    {\n        #region Constructor\n        public Country()\n        {\n\n        }\n        #endregion\n\n        #region Properties\n        /// <summary>\n        /// The unique id and primary key for this Country\n        /// </summary>\n        [Key]\n        [Required]\n        public int Id { get; set; }\n\n        /// <summary>\n        /// Country name (in UTF8 format)\n        /// </summary>\n        public string Name { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-2 format)\n        /// </summary>\n        [JsonPropertyName(\"iso2\")]\n        public string ISO2 { get; set; }\n\n        /// <summary>\n        /// Country code (in ISO 3166-1 ALPHA-3 format)\n        /// </summary>\n        [JsonPropertyName(\"iso3\")]\n        public string ISO3 { get; set; }\n        #endregion\n\n        #region Client-side properties\n        /// <summary>\n        /// The number of cities related to this country.\n        /// </summary>\n        [NotMapped]\n        public int TotCities\n        {\n            get\n            {\n                return (Cities != null)\n                    ? Cities.Count\n                    : _TotCities;\n            }\n            set { _TotCities = value; }\n        }\n\n        private int _TotCities = 0;\n        #endregion\n\n        #region Navigation Properties\n        /// <summary>\n        /// A list containing all the cities related to this country.\n        /// </summary>\n        [JsonIgnore]\n        public virtual List<City> Cities { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Pages/Error.cshtml",
    "content": "﻿@page\n@model ErrorModel\n@{\n    ViewData[\"Title\"] = \"Error\";\n}\n\n<h1 class=\"text-danger\">Error.</h1>\n<h2 class=\"text-danger\">An error occurred while processing your request.</h2>\n\n@if (Model.ShowRequestId)\n{\n    <p>\n        <strong>Request ID:</strong> <code>@Model.RequestId</code>\n    </p>\n}\n\n<h3>Development Mode</h3>\n<p>\n    Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.\n</p>\n<p>\n    <strong>The Development environment shouldn't be enabled for deployed applications.</strong>\n    It can result in displaying sensitive information from exceptions to end users.\n    For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>\n    and restarting the app.\n</p>\n"
  },
  {
    "path": "Chapter_12/WorldCities/Pages/Error.cshtml.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.AspNetCore.Mvc.RazorPages;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities.Pages\n{\n    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]\n    public class ErrorModel : PageModel\n    {\n        private readonly ILogger<ErrorModel> _logger;\n\n        public ErrorModel(ILogger<ErrorModel> logger)\n        {\n            _logger = logger;\n        }\n\n        public string RequestId { get; set; }\n\n        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);\n\n        public void OnGet()\n        {\n            RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Pages/Shared/_LoginPartial.cshtml",
    "content": "﻿@using Microsoft.AspNetCore.Identity\n@using WorldCities.Data.Models;\n@inject SignInManager<ApplicationUser> SignInManager\n@inject UserManager<ApplicationUser> UserManager\n\n@{\n    string returnUrl = null;\n    var query = ViewContext.HttpContext.Request.Query;\n    if (query.ContainsKey(\"returnUrl\"))\n    {\n        returnUrl = query[\"returnUrl\"];\n    }\n}\n\n<ul class=\"navbar-nav\">\n    @if (SignInManager.IsSignedIn(User))\n    {\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Manage/Index\" title=\"Manage\">Hello @User.Identity.Name!</a>\n        </li>\n        <li class=\"nav-item\">\n            <form class=\"form-inline\" asp-area=\"Identity\" asp-page=\"/Account/Logout\" asp-route-returnUrl=\"/\">\n                <button type=\"submit\" class=\"nav-link btn btn-link text-dark\">Logout</button>\n            </form>\n        </li>\n    }\n    else\n    {\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Register\" asp-route-returnUrl=\"@returnUrl\">Register</a>\n        </li>\n        <li class=\"nav-item\">\n            <a class=\"nav-link text-dark\" asp-area=\"Identity\" asp-page=\"/Account/Login\" asp-route-returnUrl=\"@returnUrl\">Login</a>\n        </li>\n    }\n</ul>\n"
  },
  {
    "path": "Chapter_12/WorldCities/Pages/_ViewImports.cshtml",
    "content": "@using WorldCities\n@namespace WorldCities.Pages\n@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers\n"
  },
  {
    "path": "Chapter_12/WorldCities/Program.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace WorldCities\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/Startup.cs",
    "content": "using Microsoft.AspNetCore.Authentication;\nusing Microsoft.AspNetCore.Identity;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.HttpsPolicy;\nusing Microsoft.AspNetCore.SpaServices.AngularCli;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing System.Text.Json;\nusing WorldCities.Data;\nusing WorldCities.Data.Models;\nusing Microsoft.AspNetCore.StaticFiles;\nusing Microsoft.AspNetCore.HttpOverrides;\n\nnamespace WorldCities\n{\n    public class Startup\n    {\n        public Startup(IConfiguration configuration)\n        {\n            Configuration = configuration;\n        }\n\n        public IConfiguration Configuration { get; }\n\n        // This method gets called by the runtime. Use this method to add services to the container.\n        public void ConfigureServices(IServiceCollection services)\n        {\n            services.AddControllersWithViews()\n                .AddJsonOptions(options => {\n                // set this option to TRUE to indent the JSON output\n                options.JsonSerializerOptions.WriteIndented = true;\n                // set this option to NULL to use PascalCase instead of CamelCase (default)\n                // options.JsonSerializerOptions.PropertyNamingPolicy = null;\n            });\n\n\n            // In production, the Angular files will be served from this directory\n            services.AddSpaStaticFiles(configuration =>\n            {\n                configuration.RootPath = \"ClientApp/dist\";\n            });\n\n            // Add EntityFramework support for SqlServer.\n            services.AddEntityFrameworkSqlServer();\n\n            // Add ApplicationDbContext.\n            services.AddDbContext<ApplicationDbContext>(options =>\n                options.UseSqlServer(\n                    Configuration.GetConnectionString(\"DefaultConnection\")\n                    )\n            );\n\n            // Add ASP.NET Core Identity support\n            services.AddDefaultIdentity<ApplicationUser>(options =>\n            {\n                options.SignIn.RequireConfirmedAccount = true;\n                options.Password.RequireLowercase = true;\n                options.Password.RequireUppercase = true;\n                options.Password.RequireDigit = true;\n                options.Password.RequireNonAlphanumeric = true;\n                options.Password.RequiredLength = 8;\n            })\n                .AddRoles<IdentityRole>()\n                .AddEntityFrameworkStores<ApplicationDbContext>();\n\n            services.AddIdentityServer()\n                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();\n\n            services.AddAuthentication()\n                .AddIdentityServerJwt();\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n            else\n            {\n                app.UseExceptionHandler(\"/Error\");\n                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n                app.UseHsts();\n            }\n\n            app.UseHttpsRedirection();\n\n            // add .webmanifest MIME-type support\n            FileExtensionContentTypeProvider provider = new FileExtensionContentTypeProvider();\n            provider.Mappings[\".webmanifest\"] = \"application/manifest+json\";\n\n            app.UseStaticFiles(new StaticFileOptions()\n            {\n                ContentTypeProvider = provider,\n                OnPrepareResponse = (context) =>\n                {\n                    if (context.File.Name == \"isOnline.txt\")\n                    {\n                        // disable caching for these files\n                        context.Context.Response.Headers.Add(\"Cache-Control\", \"no-cache, no-store\");\n                        context.Context.Response.Headers.Add(\"Expires\", \"-1\");\n                    }\n                }\n            });\n\n            if (!env.IsDevelopment())\n            {\n                app.UseSpaStaticFiles(new StaticFileOptions()\n                {\n                    ContentTypeProvider = provider\n                });\n            }\n\n            app.UseRouting();\n\n            // Invoke the UseForwardedHeaders middleware and configure it \n            // to forward the X-Forwarded-For and X-Forwarded-Proto headers.\n            // NOTE: This must be put BEFORE calling UseAuthentication \n            // and other authentication scheme middlewares.\n            app.UseForwardedHeaders(new ForwardedHeadersOptions\n            {\n                ForwardedHeaders = ForwardedHeaders.XForwardedFor \n                | ForwardedHeaders.XForwardedProto\n            });\n\n            app.UseAuthentication();\n            app.UseIdentityServer();\n            app.UseAuthorization();\n\n            app.UseEndpoints(endpoints =>\n            {\n                endpoints.MapControllerRoute(\n                    name: \"default\",\n                    pattern: \"{controller}/{action=Index}/{id?}\");\n\n                endpoints.MapRazorPages();\n            });\n\n            app.UseSpa(spa =>\n            {\n                // To learn more about options for serving an Angular SPA from ASP.NET Core,\n                // see https://go.microsoft.com/fwlink/?linkid=864501\n\n                spa.Options.SourcePath = \"ClientApp\";\n\n                if (env.IsDevelopment())\n                {\n                    spa.UseAngularCliServer(npmScript: \"start\");\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/WorldCities.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.1</TargetFramework>\n    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>\n    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>\n    <IsPackable>false</IsPackable>\n    <SpaRoot>ClientApp\\</SpaRoot>\n    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\\**</DefaultItemExcludes>\n\n    <!-- Set this to true if you enable server-side prerendering -->\n    <BuildServerSideRenderer>false</BuildServerSideRenderer>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"EPPlus\" Version=\"4.5.3.2\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.ApiAuthorization.IdentityServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Identity.UI\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.SpaServices.Extensions\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.EntityFrameworkCore.Tools\" Version=\"3.1.1\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"Microsoft.Extensions.Logging.Debug\" Version=\"3.1.1\" />\n    <PackageReference Include=\"Microsoft.VisualStudio.Web.CodeGeneration.Design\" Version=\"3.1.0\" />\n    <PackageReference Include=\"System.Linq.Dynamic.Core\" Version=\"1.0.20\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <!-- Don't publish the SPA source files, but do show them in the project files list -->\n    <Content Remove=\"$(SpaRoot)**\" />\n    <None Remove=\"$(SpaRoot)**\" />\n    <None Include=\"$(SpaRoot)**\" Exclude=\"$(SpaRoot)node_modules\\**\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <None Remove=\"Properties\\PublishProfiles\\AdvancedSettings.pubxml\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Folder Include=\"Data\\Migrations\\\" />\n    <Folder Include=\"Properties\\PublishProfiles\\\" />\n  </ItemGroup>\n\n  <Target Name=\"DebugEnsureNodeEnv\" BeforeTargets=\"Build\" Condition=\" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') \">\n    <!-- Ensure Node.js is installed -->\n    <Exec Command=\"node --version\" ContinueOnError=\"true\">\n      <Output TaskParameter=\"ExitCode\" PropertyName=\"ErrorCode\" />\n    </Exec>\n    <Error Condition=\"'$(ErrorCode)' != '0'\" Text=\"Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE.\" />\n    <Message Importance=\"high\" Text=\"Restoring dependencies using 'npm'. This may take several minutes...\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n  </Target>\n\n  <Target Name=\"PublishRunWebpack\" AfterTargets=\"ComputeFilesToPublish\">\n    <!-- As part of publishing, ensure the JS resources are freshly built in production mode -->\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm install\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build -- --prod\" />\n    <Exec WorkingDirectory=\"$(SpaRoot)\" Command=\"npm run build:ssr -- --prod\" Condition=\" '$(BuildServerSideRenderer)' == 'true' \" />\n\n    <!-- Include the newly-built files in the publish output -->\n    <ItemGroup>\n      <DistFiles Include=\"$(SpaRoot)dist\\**; $(SpaRoot)dist-server\\**\" />\n      <DistFiles Include=\"$(SpaRoot)node_modules\\**\" Condition=\"'$(BuildServerSideRenderer)' == 'true'\" />\n      <ResolvedFileToPublish Include=\"@(DistFiles->'%(FullPath)')\" Exclude=\"@(ResolvedFileToPublish)\">\n        <RelativePath>%(DistFiles.Identity)</RelativePath>\n        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>\n        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>\n      </ResolvedFileToPublish>\n    </ItemGroup>\n  </Target>\n\n</Project>\n"
  },
  {
    "path": "Chapter_12/WorldCities/_LinuxVM_ConfigFiles/kestrel-worldcities.service",
    "content": "﻿[Unit]\nDescription=WorldCities\n\n[Service]\nWorkingDirectory=/var/www/WorldCities\nExecStart=/usr/bin/dotnet /var/www/WorldCities/WorldCities.dll\nRestart=always\n# Restart service after 10 seconds if the dotnet service crashes:\nRestartSec=10\nKillSignal=SIGINT\nSyslogIdentifier=WorldCities\nUser=nginx\nEnvironment=ASPNETCORE_ENVIRONMENT=Production\nEnvironment=DOTNET_PRINT_TELEMETRY_MESSAGE=false\nEnvironment=ASPNETCORE_URLS=http://localhost:5000\n\n# How many seconds to wait for the app to shut down after it receives the initi$\n# If the app doesn't shut down in this period, SIGKILL is issued to terminate t$\n# The default timeout for most distributions is 90 seconds.\nTimeoutStopSec=90\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "Chapter_12/WorldCities/_LinuxVM_ConfigFiles/nginx-worldcities.conf",
    "content": "﻿server {\n    listen 443 ssl http2;\n    listen [::]:443 ssl http2;\n        \n    server_name  worldcities.io;\n\n    ssl_certificate /var/ssl/worldcities.crt;\n    ssl_certificate_key /var/ssl/worldcities.key;\n\n    root         /var/www/WorldCities/;\n    index        index.html;\n    autoindex off;\n\n    location / {\n        proxy_pass http://localhost:5000;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection keep-alive;\n        proxy_set_header Host $host;\n        proxy_cache_bypass $http_upgrade;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n\n\n\n"
  },
  {
    "path": "Chapter_12/WorldCities/_LinuxVM_ConfigFiles/nginx.conf",
    "content": "﻿# For more information on configuration, see:\n#   * Official English Documentation: http://nginx.org/en/docs/\n#   * Official Russian Documentation: http://nginx.org/ru/docs/\n\nuser nginx;\nworker_processes auto;\nerror_log /var/log/nginx/error.log;\npid /run/nginx.pid;\n\n# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.\ninclude /usr/share/nginx/modules/*.conf;\n\nevents {\n    worker_connections 1024;\n}\n\nhttp {\n    log_format  main  '$remote_addr - $remote_user [$time_local] \"$request\" '\n                      '$status $body_bytes_sent \"$http_referer\" '\n                      '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n\n    access_log  /var/log/nginx/access.log  main;\n\n    sendfile            on;\n    tcp_nopush          on;\n    tcp_nodelay         on;\n    keepalive_timeout   65;\n    types_hash_max_size 2048;\n\n    include             /etc/nginx/mime.types;\n    default_type        application/octet-stream;\n\n    # Load modular configuration files from the /etc/nginx/conf.d directory.\n    # See http://nginx.org/en/docs/ngx_core_module.html#include\n    # for more information.\n    include /etc/nginx/conf.d/*.conf;\n\n    server {\n        listen       80 default_server;\n        listen       [::]:80 default_server;\n        server_name  _;\n        root         /usr/share/nginx/html;\n\n        # Load configuration files for the default server block.\n        include /etc/nginx/default.d/*.conf;\n\n        location / {\n        }\n\n        error_page 404 /404.html;\n            location = /40x.html {\n        }\n\n        error_page 500 502 503 504 /50x.html;\n            location = /50x.html {\n        }\n    }\n\n    include nginx-worldcities.conf;\n\n}\n\n"
  },
  {
    "path": "Chapter_12/WorldCities/appsettings.Development.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"Server=localhost\\\\SQLEXPRESS;Database=WorldCities;User Id=WorldCities;Password=MyVeryOwn$721;Integrated Security=False;MultipleActiveResultSets=True\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Debug\",\n      \"System\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  },\n  \"IdentityServer\": {\n    \"Key\": {\n      \"Type\": \"Development\"\n    }\n  }\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/appsettings.json",
    "content": "{\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"Server=netcore3-angular8.database.windows.net;Database=WorldCities;User Id=netcore3-angular8;Password=MyVeryOwnPassword$;Integrated Security=False;MultipleActiveResultSets=True\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Warning\"\n    }\n  },\n  \"IdentityServer\": {\n    \"Clients\": {\n      \"WorldCities\": {\n        \"Profile\": \"IdentityServerSPA\"\n      }\n    },\n    \"Key\": {\n      \"Type\": \"File\",\n      \"FilePath\": \"/var/ssl/worldcities.pfx\",\n      \"Password\": \"MyVerySecretCAPassword$\"\n    }\n  },\n  \"AllowedHosts\": \"*\"\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/wwwroot/isOnline.txt",
    "content": "﻿."
  },
  {
    "path": "Chapter_12/WorldCities/wwwroot/manifest.webmanifest",
    "content": "{\n  \"name\": \"HealthCheck\",\n  \"short_name\": \"HealthCheck\",\n  \"theme_color\": \"#2196f3\",\n  \"background_color\": \"#2196f3\",\n  \"display\": \"standalone\",\n  \"Scope\": \"/\",\n  \"start_url\": \"/\",\n  \"icons\": [\n    {\n      \"src\": \"assets/icons/icon-72x72.png\",\n      \"sizes\": \"72x72\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-96x96.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-128x128.png\",\n      \"sizes\": \"128x128\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-144x144.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-152x152.png\",\n      \"sizes\": \"152x152\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-384x384.png\",\n      \"sizes\": \"384x384\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/icons/icon-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"splash_pages\": null\n}\n"
  },
  {
    "path": "Chapter_12/WorldCities/wwwroot/ngsw-worker.js",
    "content": "(function () {\n    'use strict';\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * Adapts the service worker to its runtime environment.\n     *\n     * Mostly, this is used to mock out identifiers which are otherwise read\n     * from the global scope.\n     */\n    class Adapter {\n        constructor(scope) {\n            // Suffixing `ngsw` with the baseHref to avoid clash of cache names\n            // for SWs with different scopes on the same domain.\n            const baseHref = this.parseUrl(scope.registration.scope).path;\n            this.cacheNamePrefix = 'ngsw:' + baseHref;\n        }\n        /**\n         * Wrapper around the `Request` constructor.\n         */\n        newRequest(input, init) {\n            return new Request(input, init);\n        }\n        /**\n         * Wrapper around the `Response` constructor.\n         */\n        newResponse(body, init) { return new Response(body, init); }\n        /**\n         * Wrapper around the `Headers` constructor.\n         */\n        newHeaders(headers) { return new Headers(headers); }\n        /**\n         * Test if a given object is an instance of `Client`.\n         */\n        isClient(source) { return (source instanceof Client); }\n        /**\n         * Read the current UNIX time in milliseconds.\n         */\n        get time() { return Date.now(); }\n        /**\n         * Extract the pathname of a URL.\n         */\n        parseUrl(url, relativeTo) {\n            // Workaround a Safari bug, see\n            // https://github.com/angular/angular/issues/31061#issuecomment-503637978\n            const parsed = !relativeTo ? new URL(url) : new URL(url, relativeTo);\n            return { origin: parsed.origin, path: parsed.pathname, search: parsed.search };\n        }\n        /**\n         * Wait for a given amount of time before completing a Promise.\n         */\n        timeout(ms) {\n            return new Promise(resolve => { setTimeout(() => resolve(), ms); });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * An error returned in rejected promises if the given key is not found in the table.\n     */\n    class NotFound {\n        constructor(table, key) {\n            this.table = table;\n            this.key = key;\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * An implementation of a `Database` that uses the `CacheStorage` API to serialize\n     * state within mock `Response` objects.\n     */\n    class CacheDatabase {\n        constructor(scope, adapter) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.tables = new Map();\n        }\n        'delete'(name) {\n            if (this.tables.has(name)) {\n                this.tables.delete(name);\n            }\n            return this.scope.caches.delete(`${this.adapter.cacheNamePrefix}:db:${name}`);\n        }\n        list() {\n            return this.scope.caches.keys().then(keys => keys.filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:db:`)));\n        }\n        open(name) {\n            if (!this.tables.has(name)) {\n                const table = this.scope.caches.open(`${this.adapter.cacheNamePrefix}:db:${name}`)\n                    .then(cache => new CacheTable(name, cache, this.adapter));\n                this.tables.set(name, table);\n            }\n            return this.tables.get(name);\n        }\n    }\n    /**\n     * A `Table` backed by a `Cache`.\n     */\n    class CacheTable {\n        constructor(table, cache, adapter) {\n            this.table = table;\n            this.cache = cache;\n            this.adapter = adapter;\n        }\n        request(key) { return this.adapter.newRequest('/' + key); }\n        'delete'(key) { return this.cache.delete(this.request(key)); }\n        keys() {\n            return this.cache.keys().then(requests => requests.map(req => req.url.substr(1)));\n        }\n        read(key) {\n            return this.cache.match(this.request(key)).then(res => {\n                if (res === undefined) {\n                    return Promise.reject(new NotFound(this.table, key));\n                }\n                return res.json();\n            });\n        }\n        write(key, value) {\n            return this.cache.put(this.request(key), this.adapter.newResponse(JSON.stringify(value)));\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var UpdateCacheStatus;\n    (function (UpdateCacheStatus) {\n        UpdateCacheStatus[UpdateCacheStatus[\"NOT_CACHED\"] = 0] = \"NOT_CACHED\";\n        UpdateCacheStatus[UpdateCacheStatus[\"CACHED_BUT_UNUSED\"] = 1] = \"CACHED_BUT_UNUSED\";\n        UpdateCacheStatus[UpdateCacheStatus[\"CACHED\"] = 2] = \"CACHED\";\n    })(UpdateCacheStatus || (UpdateCacheStatus = {}));\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    class SwCriticalError extends Error {\n        constructor() {\n            super(...arguments);\n            this.isCritical = true;\n        }\n    }\n    function errorToString(error) {\n        if (error instanceof Error) {\n            return `${error.message}\\n${error.stack}`;\n        }\n        else {\n            return `${error}`;\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    /**\n     * Compute the SHA1 of the given string\n     *\n     * see http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf\n     *\n     * WARNING: this function has not been designed not tested with security in mind.\n     *          DO NOT USE IT IN A SECURITY SENSITIVE CONTEXT.\n     *\n     * Borrowed from @angular/compiler/src/i18n/digest.ts\n     */\n    function sha1(str) {\n        const utf8 = str;\n        const words32 = stringToWords32(utf8, Endian.Big);\n        return _sha1(words32, utf8.length * 8);\n    }\n    function sha1Binary(buffer) {\n        const words32 = arrayBufferToWords32(buffer, Endian.Big);\n        return _sha1(words32, buffer.byteLength * 8);\n    }\n    function _sha1(words32, len) {\n        const w = [];\n        let [a, b, c, d, e] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0];\n        words32[len >> 5] |= 0x80 << (24 - len % 32);\n        words32[((len + 64 >> 9) << 4) + 15] = len;\n        for (let i = 0; i < words32.length; i += 16) {\n            const [h0, h1, h2, h3, h4] = [a, b, c, d, e];\n            for (let j = 0; j < 80; j++) {\n                if (j < 16) {\n                    w[j] = words32[i + j];\n                }\n                else {\n                    w[j] = rol32(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);\n                }\n                const [f, k] = fk(j, b, c, d);\n                const temp = [rol32(a, 5), f, e, k, w[j]].reduce(add32);\n                [e, d, c, b, a] = [d, c, rol32(b, 30), a, temp];\n            }\n            [a, b, c, d, e] = [add32(a, h0), add32(b, h1), add32(c, h2), add32(d, h3), add32(e, h4)];\n        }\n        return byteStringToHexString(words32ToByteString([a, b, c, d, e]));\n    }\n    function add32(a, b) {\n        return add32to64(a, b)[1];\n    }\n    function add32to64(a, b) {\n        const low = (a & 0xffff) + (b & 0xffff);\n        const high = (a >>> 16) + (b >>> 16) + (low >>> 16);\n        return [high >>> 16, (high << 16) | (low & 0xffff)];\n    }\n    // Rotate a 32b number left `count` position\n    function rol32(a, count) {\n        return (a << count) | (a >>> (32 - count));\n    }\n    var Endian;\n    (function (Endian) {\n        Endian[Endian[\"Little\"] = 0] = \"Little\";\n        Endian[Endian[\"Big\"] = 1] = \"Big\";\n    })(Endian || (Endian = {}));\n    function fk(index, b, c, d) {\n        if (index < 20) {\n            return [(b & c) | (~b & d), 0x5a827999];\n        }\n        if (index < 40) {\n            return [b ^ c ^ d, 0x6ed9eba1];\n        }\n        if (index < 60) {\n            return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];\n        }\n        return [b ^ c ^ d, 0xca62c1d6];\n    }\n    function stringToWords32(str, endian) {\n        const size = (str.length + 3) >>> 2;\n        const words32 = [];\n        for (let i = 0; i < size; i++) {\n            words32[i] = wordAt(str, i * 4, endian);\n        }\n        return words32;\n    }\n    function arrayBufferToWords32(buffer, endian) {\n        const size = (buffer.byteLength + 3) >>> 2;\n        const words32 = [];\n        const view = new Uint8Array(buffer);\n        for (let i = 0; i < size; i++) {\n            words32[i] = wordAt(view, i * 4, endian);\n        }\n        return words32;\n    }\n    function byteAt(str, index) {\n        if (typeof str === 'string') {\n            return index >= str.length ? 0 : str.charCodeAt(index) & 0xff;\n        }\n        else {\n            return index >= str.byteLength ? 0 : str[index] & 0xff;\n        }\n    }\n    function wordAt(str, index, endian) {\n        let word = 0;\n        if (endian === Endian.Big) {\n            for (let i = 0; i < 4; i++) {\n                word += byteAt(str, index + i) << (24 - 8 * i);\n            }\n        }\n        else {\n            for (let i = 0; i < 4; i++) {\n                word += byteAt(str, index + i) << 8 * i;\n            }\n        }\n        return word;\n    }\n    function words32ToByteString(words32) {\n        return words32.reduce((str, word) => str + word32ToByteString(word), '');\n    }\n    function word32ToByteString(word) {\n        let str = '';\n        for (let i = 0; i < 4; i++) {\n            str += String.fromCharCode((word >>> 8 * (3 - i)) & 0xff);\n        }\n        return str;\n    }\n    function byteStringToHexString(str) {\n        let hex = '';\n        for (let i = 0; i < str.length; i++) {\n            const b = byteAt(str, i);\n            hex += (b >>> 4).toString(16) + (b & 0x0f).toString(16);\n        }\n        return hex.toLowerCase();\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    /**\n     * A group of assets that are cached in a `Cache` and managed by a given policy.\n     *\n     * Concrete classes derive from this base and specify the exact caching policy.\n     */\n    class AssetGroup {\n        constructor(scope, adapter, idle, config, hashes, db, prefix) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.idle = idle;\n            this.config = config;\n            this.hashes = hashes;\n            this.db = db;\n            this.prefix = prefix;\n            /**\n             * A deduplication cache, to make sure the SW never makes two network requests\n             * for the same resource at once. Managed by `fetchAndCacheOnce`.\n             */\n            this.inFlightRequests = new Map();\n            /**\n             * Regular expression patterns.\n             */\n            this.patterns = [];\n            this.name = config.name;\n            // Patterns in the config are regular expressions disguised as strings. Breathe life into them.\n            this.patterns = this.config.patterns.map(pattern => new RegExp(pattern));\n            // This is the primary cache, which holds all of the cached requests for this group. If a\n            // resource\n            // isn't in this cache, it hasn't been fetched yet.\n            this.cache = this.scope.caches.open(`${this.prefix}:${this.config.name}:cache`);\n            // This is the metadata table, which holds specific information for each cached URL, such as\n            // the timestamp of when it was added to the cache.\n            this.metadata = this.db.open(`${this.prefix}:${this.config.name}:meta`);\n            // Determine the origin from the registration scope. This is used to differentiate between\n            // relative and absolute URLs.\n            this.origin = this.adapter.parseUrl(this.scope.registration.scope).origin;\n        }\n        cacheStatus(url) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                const meta = yield this.metadata;\n                const res = yield cache.match(this.adapter.newRequest(url));\n                if (res === undefined) {\n                    return UpdateCacheStatus.NOT_CACHED;\n                }\n                try {\n                    const data = yield meta.read(url);\n                    if (!data.used) {\n                        return UpdateCacheStatus.CACHED_BUT_UNUSED;\n                    }\n                }\n                catch (_) {\n                    // Error on the side of safety and assume cached.\n                }\n                return UpdateCacheStatus.CACHED;\n            });\n        }\n        /**\n         * Clean up all the cached data for this group.\n         */\n        cleanup() {\n            return __awaiter(this, void 0, void 0, function* () {\n                yield this.scope.caches.delete(`${this.prefix}:${this.config.name}:cache`);\n                yield this.db.delete(`${this.prefix}:${this.config.name}:meta`);\n            });\n        }\n        /**\n         * Process a request for a given resource and return it, or return null if it's not available.\n         */\n        handleFetch(req, ctx) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                // Either the request matches one of the known resource URLs, one of the patterns for\n                // dynamically matched URLs, or neither. Determine which is the case for this request in\n                // order to decide how to handle it.\n                if (this.config.urls.indexOf(url) !== -1 || this.patterns.some(pattern => pattern.test(url))) {\n                    // This URL matches a known resource. Either it's been cached already or it's missing, in\n                    // which case it needs to be loaded from the network.\n                    // Open the cache to check whether this resource is present.\n                    const cache = yield this.cache;\n                    // Look for a cached response. If one exists, it can be used to resolve the fetch\n                    // operation.\n                    const cachedResponse = yield cache.match(req);\n                    if (cachedResponse !== undefined) {\n                        // A response has already been cached (which presumably matches the hash for this\n                        // resource). Check whether it's safe to serve this resource from cache.\n                        if (this.hashes.has(url)) {\n                            // This resource has a hash, and thus is versioned by the manifest. It's safe to return\n                            // the response.\n                            return cachedResponse;\n                        }\n                        else {\n                            // This resource has no hash, and yet exists in the cache. Check how old this request is\n                            // to make sure it's still usable.\n                            if (yield this.needToRevalidate(req, cachedResponse)) {\n                                this.idle.schedule(`revalidate(${this.prefix}, ${this.config.name}): ${req.url}`, () => __awaiter(this, void 0, void 0, function* () { yield this.fetchAndCacheOnce(req); }));\n                            }\n                            // In either case (revalidation or not), the cached response must be good.\n                            return cachedResponse;\n                        }\n                    }\n                    // No already-cached response exists, so attempt a fetch/cache operation. The original request\n                    // may specify things like credential inclusion, but for assets these are not honored in order\n                    // to avoid issues with opaque responses. The SW requests the data itself.\n                    const res = yield this.fetchAndCacheOnce(this.adapter.newRequest(req.url));\n                    // If this is successful, the response needs to be cloned as it might be used to respond to\n                    // multiple fetch operations at the same time.\n                    return res.clone();\n                }\n                else {\n                    return null;\n                }\n            });\n        }\n        getConfigUrl(url) {\n            // If the URL is relative to the SW's own origin, then only consider the path relative to\n            // the domain root. Determine this by checking the URL's origin against the SW's.\n            const parsed = this.adapter.parseUrl(url, this.scope.registration.scope);\n            if (parsed.origin === this.origin) {\n                // The URL is relative to the SW's origin domain.\n                return parsed.path;\n            }\n            else {\n                return url;\n            }\n        }\n        /**\n         * Some resources are cached without a hash, meaning that their expiration is controlled\n         * by HTTP caching headers. Check whether the given request/response pair is still valid\n         * per the caching headers.\n         */\n        needToRevalidate(req, res) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Three different strategies apply here:\n                // 1) The request has a Cache-Control header, and thus expiration needs to be based on its age.\n                // 2) The request has an Expires header, and expiration is based on the current timestamp.\n                // 3) The request has no applicable caching headers, and must be revalidated.\n                if (res.headers.has('Cache-Control')) {\n                    // Figure out if there is a max-age directive in the Cache-Control header.\n                    const cacheControl = res.headers.get('Cache-Control');\n                    const cacheDirectives = cacheControl\n                        // Directives are comma-separated within the Cache-Control header value.\n                        .split(',')\n                        // Make sure each directive doesn't have extraneous whitespace.\n                        .map(v => v.trim())\n                        // Some directives have values (like maxage and s-maxage)\n                        .map(v => v.split('='));\n                    // Lowercase all the directive names.\n                    cacheDirectives.forEach(v => v[0] = v[0].toLowerCase());\n                    // Find the max-age directive, if one exists.\n                    const maxAgeDirective = cacheDirectives.find(v => v[0] === 'max-age');\n                    const cacheAge = maxAgeDirective ? maxAgeDirective[1] : undefined;\n                    if (!cacheAge) {\n                        // No usable TTL defined. Must assume that the response is stale.\n                        return true;\n                    }\n                    try {\n                        const maxAge = 1000 * parseInt(cacheAge);\n                        // Determine the origin time of this request. If the SW has metadata on the request (which\n                        // it\n                        // should), it will have the time the request was added to the cache. If it doesn't for some\n                        // reason, the request may have a Date header which will serve the same purpose.\n                        let ts;\n                        try {\n                            // Check the metadata table. If a timestamp is there, use it.\n                            const metaTable = yield this.metadata;\n                            ts = (yield metaTable.read(req.url)).ts;\n                        }\n                        catch (_a) {\n                            // Otherwise, look for a Date header.\n                            const date = res.headers.get('Date');\n                            if (date === null) {\n                                // Unable to determine when this response was created. Assume that it's stale, and\n                                // revalidate it.\n                                return true;\n                            }\n                            ts = Date.parse(date);\n                        }\n                        const age = this.adapter.time - ts;\n                        return age < 0 || age > maxAge;\n                    }\n                    catch (_b) {\n                        // Assume stale.\n                        return true;\n                    }\n                }\n                else if (res.headers.has('Expires')) {\n                    // Determine if the expiration time has passed.\n                    const expiresStr = res.headers.get('Expires');\n                    try {\n                        // The request needs to be revalidated if the current time is later than the expiration\n                        // time, if it parses correctly.\n                        return this.adapter.time > Date.parse(expiresStr);\n                    }\n                    catch (_c) {\n                        // The expiration date failed to parse, so revalidate as a precaution.\n                        return true;\n                    }\n                }\n                else {\n                    // No way to evaluate staleness, so assume the response is already stale.\n                    return true;\n                }\n            });\n        }\n        /**\n         * Fetch the complete state of a cached resource, or return null if it's not found.\n         */\n        fetchFromCacheOnly(url) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                const metaTable = yield this.metadata;\n                // Lookup the response in the cache.\n                const response = yield cache.match(this.adapter.newRequest(url));\n                if (response === undefined) {\n                    // It's not found, return null.\n                    return null;\n                }\n                // Next, lookup the cached metadata.\n                let metadata = undefined;\n                try {\n                    metadata = yield metaTable.read(url);\n                }\n                catch (_a) {\n                    // Do nothing, not found. This shouldn't happen, but it can be handled.\n                }\n                // Return both the response and any available metadata.\n                return { response, metadata };\n            });\n        }\n        /**\n         * Lookup all resources currently stored in the cache which have no associated hash.\n         */\n        unhashedResources() {\n            return __awaiter(this, void 0, void 0, function* () {\n                const cache = yield this.cache;\n                // Start with the set of all cached URLs.\n                return (yield cache.keys())\n                    .map(request => request.url)\n                    // Exclude the URLs which have hashes.\n                    .filter(url => !this.hashes.has(url));\n            });\n        }\n        /**\n         * Fetch the given resource from the network, and cache it if able.\n         */\n        fetchAndCacheOnce(req, used = true) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // The `inFlightRequests` map holds information about which caching operations are currently\n                // underway for known resources. If this request appears there, another \"thread\" is already\n                // in the process of caching it, and this work should not be duplicated.\n                if (this.inFlightRequests.has(req.url)) {\n                    // There is a caching operation already in progress for this request. Wait for it to\n                    // complete, and hopefully it will have yielded a useful response.\n                    return this.inFlightRequests.get(req.url);\n                }\n                // No other caching operation is being attempted for this resource, so it will be owned here.\n                // Go to the network and get the correct version.\n                const fetchOp = this.fetchFromNetwork(req);\n                // Save this operation in `inFlightRequests` so any other \"thread\" attempting to cache it\n                // will block on this chain instead of duplicating effort.\n                this.inFlightRequests.set(req.url, fetchOp);\n                // Make sure this attempt is cleaned up properly on failure.\n                try {\n                    // Wait for a response. If this fails, the request will remain in `inFlightRequests`\n                    // indefinitely.\n                    const res = yield fetchOp;\n                    // It's very important that only successful responses are cached. Unsuccessful responses\n                    // should never be cached as this can completely break applications.\n                    if (!res.ok) {\n                        throw new Error(`Response not Ok (fetchAndCacheOnce): request for ${req.url} returned response ${res.status} ${res.statusText}`);\n                    }\n                    try {\n                        // This response is safe to cache (as long as it's cloned). Wait until the cache operation\n                        // is complete.\n                        const cache = yield this.scope.caches.open(`${this.prefix}:${this.config.name}:cache`);\n                        yield cache.put(req, res.clone());\n                        // If the request is not hashed, update its metadata, especially the timestamp. This is\n                        // needed for future determination of whether this cached response is stale or not.\n                        if (!this.hashes.has(req.url)) {\n                            // Metadata is tracked for requests that are unhashed.\n                            const meta = { ts: this.adapter.time, used };\n                            const metaTable = yield this.metadata;\n                            yield metaTable.write(req.url, meta);\n                        }\n                        return res;\n                    }\n                    catch (err) {\n                        // Among other cases, this can happen when the user clears all data through the DevTools,\n                        // but the SW is still running and serving another tab. In that case, trying to write to the\n                        // caches throws an `Entry was not found` error.\n                        // If this happens the SW can no longer work correctly. This situation is unrecoverable.\n                        throw new SwCriticalError(`Failed to update the caches for request to '${req.url}' (fetchAndCacheOnce): ${errorToString(err)}`);\n                    }\n                }\n                finally {\n                    // Finally, it can be removed from `inFlightRequests`. This might result in a double-remove\n                    // if some other chain was already making this request too, but that won't hurt anything.\n                    this.inFlightRequests.delete(req.url);\n                }\n            });\n        }\n        fetchFromNetwork(req, redirectLimit = 3) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Make a cache-busted request for the resource.\n                const res = yield this.cacheBustedFetchFromNetwork(req);\n                // Check for redirected responses, and follow the redirects.\n                if (res['redirected'] && !!res.url) {\n                    // If the redirect limit is exhausted, fail with an error.\n                    if (redirectLimit === 0) {\n                        throw new SwCriticalError(`Response hit redirect limit (fetchFromNetwork): request redirected too many times, next is ${res.url}`);\n                    }\n                    // Unwrap the redirect directly.\n                    return this.fetchFromNetwork(this.adapter.newRequest(res.url), redirectLimit - 1);\n                }\n                return res;\n            });\n        }\n        /**\n         * Load a particular asset from the network, accounting for hash validation.\n         */\n        cacheBustedFetchFromNetwork(req) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                // If a hash is available for this resource, then compare the fetched version with the\n                // canonical hash. Otherwise, the network version will have to be trusted.\n                if (this.hashes.has(url)) {\n                    // It turns out this resource does have a hash. Look it up. Unless the fetched version\n                    // matches this hash, it's invalid and the whole manifest may need to be thrown out.\n                    const canonicalHash = this.hashes.get(url);\n                    // Ideally, the resource would be requested with cache-busting to guarantee the SW gets\n                    // the freshest version. However, doing this would eliminate any chance of the response\n                    // being in the HTTP cache. Given that the browser has recently actively loaded the page,\n                    // it's likely that many of the responses the SW needs to cache are in the HTTP cache and\n                    // are fresh enough to use. In the future, this could be done by setting cacheMode to\n                    // *only* check the browser cache for a cached version of the resource, when cacheMode is\n                    // fully supported. For now, the resource is fetched directly, without cache-busting, and\n                    // if the hash test fails a cache-busted request is tried before concluding that the\n                    // resource isn't correct. This gives the benefit of acceleration via the HTTP cache\n                    // without the risk of stale data, at the expense of a duplicate request in the event of\n                    // a stale response.\n                    // Fetch the resource from the network (possibly hitting the HTTP cache).\n                    const networkResult = yield this.safeFetch(req);\n                    // Decide whether a cache-busted request is necessary. It might be for two independent\n                    // reasons: either the non-cache-busted request failed (hopefully transiently) or if the\n                    // hash of the content retrieved does not match the canonical hash from the manifest. It's\n                    // only valid to access the content of the first response if the request was successful.\n                    let makeCacheBustedRequest = networkResult.ok;\n                    if (makeCacheBustedRequest) {\n                        // The request was successful. A cache-busted request is only necessary if the hashes\n                        // don't match. Compare them, making sure to clone the response so it can be used later\n                        // if it proves to be valid.\n                        const fetchedHash = sha1Binary(yield networkResult.clone().arrayBuffer());\n                        makeCacheBustedRequest = (fetchedHash !== canonicalHash);\n                    }\n                    // Make a cache busted request to the network, if necessary.\n                    if (makeCacheBustedRequest) {\n                        // Hash failure, the version that was retrieved under the default URL did not have the\n                        // hash expected. This could be because the HTTP cache got in the way and returned stale\n                        // data, or because the version on the server really doesn't match. A cache-busting\n                        // request will differentiate these two situations.\n                        // TODO: handle case where the URL has parameters already (unlikely for assets).\n                        const cacheBustReq = this.adapter.newRequest(this.cacheBust(req.url));\n                        const cacheBustedResult = yield this.safeFetch(cacheBustReq);\n                        // If the response was unsuccessful, there's nothing more that can be done.\n                        if (!cacheBustedResult.ok) {\n                            throw new SwCriticalError(`Response not Ok (cacheBustedFetchFromNetwork): cache busted request for ${req.url} returned response ${cacheBustedResult.status} ${cacheBustedResult.statusText}`);\n                        }\n                        // Hash the contents.\n                        const cacheBustedHash = sha1Binary(yield cacheBustedResult.clone().arrayBuffer());\n                        // If the cache-busted version doesn't match, then the manifest is not an accurate\n                        // representation of the server's current set of files, and the SW should give up.\n                        if (canonicalHash !== cacheBustedHash) {\n                            throw new SwCriticalError(`Hash mismatch (cacheBustedFetchFromNetwork): ${req.url}: expected ${canonicalHash}, got ${cacheBustedHash} (after cache busting)`);\n                        }\n                        // If it does match, then use the cache-busted result.\n                        return cacheBustedResult;\n                    }\n                    // Excellent, the version from the network matched on the first try, with no need for\n                    // cache-busting. Use it.\n                    return networkResult;\n                }\n                else {\n                    // This URL doesn't exist in our hash database, so it must be requested directly.\n                    return this.safeFetch(req);\n                }\n            });\n        }\n        /**\n         * Possibly update a resource, if it's expired and needs to be updated. A no-op otherwise.\n         */\n        maybeUpdate(updateFrom, req, cache) {\n            return __awaiter(this, void 0, void 0, function* () {\n                const url = this.getConfigUrl(req.url);\n                const meta = yield this.metadata;\n                // Check if this resource is hashed and already exists in the cache of a prior version.\n                if (this.hashes.has(url)) {\n                    const hash = this.hashes.get(url);\n                    // Check the caches of prior versions, using the hash to ensure the correct version of\n                    // the resource is loaded.\n                    const res = yield updateFrom.lookupResourceWithHash(url, hash);\n                    // If a previously cached version was available, copy it over to this cache.\n                    if (res !== null) {\n                        // Copy to this cache.\n                        yield cache.put(req, res);\n                        yield meta.write(req.url, { ts: this.adapter.time, used: false });\n                        // No need to do anything further with this resource, it's now cached properly.\n                        return true;\n                    }\n                }\n                // No up-to-date version of this resource could be found.\n                return false;\n            });\n        }\n        /**\n         * Construct a cache-busting URL for a given URL.\n         */\n        cacheBust(url) {\n            return url + (url.indexOf('?') === -1 ? '?' : '&') + 'ngsw-cache-bust=' + Math.random();\n        }\n        safeFetch(req) {\n            return __awaiter(this, void 0, void 0, function* () {\n                try {\n                    return yield this.scope.fetch(req);\n                }\n                catch (_a) {\n                    return this.adapter.newResponse('', {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n    /**\n     * An `AssetGroup` that prefetches all of its resources during initialization.\n     */\n    class PrefetchAssetGroup extends AssetGroup {\n        initializeFully(updateFrom) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // Open the cache which actually holds requests.\n                const cache = yield this.cache;\n                // Cache all known resources serially. As this reduce proceeds, each Promise waits\n                // on the last before starting the fetch/cache operation for the next request. Any\n                // errors cause fall-through to the final Promise which rejects.\n                yield this.config.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                    // Wait on all previous operations to complete.\n                    yield previous;\n                    // Construct the Request for this url.\n                    const req = this.adapter.newRequest(url);\n                    // First, check the cache to see if there is already a copy of this resource.\n                    const alreadyCached = (yield cache.match(req)) !== undefined;\n                    // If the resource is in the cache already, it can be skipped.\n                    if (alreadyCached) {\n                        return;\n                    }\n                    // If an update source is available.\n                    if (updateFrom !== undefined && (yield this.maybeUpdate(updateFrom, req, cache))) {\n                        return;\n                    }\n                    // Otherwise, go to the network and hopefully cache the response (if successful).\n                    yield this.fetchAndCacheOnce(req, false);\n                }), Promise.resolve());\n                // Handle updating of unknown (unhashed) resources. This is only possible if there's\n                // a source to update from.\n                if (updateFrom !== undefined) {\n                    const metaTable = yield this.metadata;\n                    // Select all of the previously cached resources. These are cached unhashed resources\n                    // from previous versions of the app, in any asset group.\n                    yield (yield updateFrom.previouslyCachedResources())\n                        // First, narrow down the set of resources to those which are handled by this group.\n                        // Either it's a known URL, or it matches a given pattern.\n                        .filter(url => this.config.urls.some(cacheUrl => cacheUrl === url) ||\n                        this.patterns.some(pattern => pattern.test(url)))\n                        // Finally, process each resource in turn.\n                        .reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                        yield previous;\n                        const req = this.adapter.newRequest(url);\n                        // It's possible that the resource in question is already cached. If so,\n                        // continue to the next one.\n                        const alreadyCached = ((yield cache.match(req)) !== undefined);\n                        if (alreadyCached) {\n                            return;\n                        }\n                        // Get the most recent old version of the resource.\n                        const res = yield updateFrom.lookupResourceWithoutHash(url);\n                        if (res === null || res.metadata === undefined) {\n                            // Unexpected, but not harmful.\n                            return;\n                        }\n                        // Write it into the cache. It may already be expired, but it can still serve\n                        // traffic until it's updated (stale-while-revalidate approach).\n                        yield cache.put(req, res.response);\n                        yield metaTable.write(url, Object.assign(Object.assign({}, res.metadata), { used: false }));\n                    }), Promise.resolve());\n                }\n            });\n        }\n    }\n    class LazyAssetGroup extends AssetGroup {\n        initializeFully(updateFrom) {\n            return __awaiter(this, void 0, void 0, function* () {\n                // No action necessary if no update source is available - resources managed in this group\n                // are all lazily loaded, so there's nothing to initialize.\n                if (updateFrom === undefined) {\n                    return;\n                }\n                // Open the cache which actually holds requests.\n                const cache = yield this.cache;\n                // Loop through the listed resources, caching any which are available.\n                yield this.config.urls.reduce((previous, url) => __awaiter(this, void 0, void 0, function* () {\n                    // Wait on all previous operations to complete.\n                    yield previous;\n                    // Construct the Request for this url.\n                    const req = this.adapter.newRequest(url);\n                    // First, check the cache to see if there is already a copy of this resource.\n                    const alreadyCached = (yield cache.match(req)) !== undefined;\n                    // If the resource is in the cache already, it can be skipped.\n                    if (alreadyCached) {\n                        return;\n                    }\n                    const updated = yield this.maybeUpdate(updateFrom, req, cache);\n                    if (this.config.updateMode === 'prefetch' && !updated) {\n                        // If the resource was not updated, either it was not cached before or\n                        // the previously cached version didn't match the updated hash. In that\n                        // case, prefetch update mode dictates that the resource will be updated,\n                        // except if it was not previously utilized. Check the status of the\n                        // cached resource to see.\n                        const cacheStatus = yield updateFrom.recentCacheStatus(url);\n                        // If the resource is not cached, or was cached but unused, then it will be\n                        // loaded lazily.\n                        if (cacheStatus !== UpdateCacheStatus.CACHED) {\n                            return;\n                        }\n                        // Update from the network.\n                        yield this.fetchAndCacheOnce(req, false);\n                    }\n                }), Promise.resolve());\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    /**\n     * Manages an instance of `LruState` and moves URLs to the head of the\n     * chain when requested.\n     */\n    class LruList {\n        constructor(state) {\n            if (state === undefined) {\n                state = {\n                    head: null,\n                    tail: null,\n                    map: {},\n                    count: 0,\n                };\n            }\n            this.state = state;\n        }\n        /**\n         * The current count of URLs in the list.\n         */\n        get size() { return this.state.count; }\n        /**\n         * Remove the tail.\n         */\n        pop() {\n            // If there is no tail, return null.\n            if (this.state.tail === null) {\n                return null;\n            }\n            const url = this.state.tail;\n            this.remove(url);\n            // This URL has been successfully evicted.\n            return url;\n        }\n        remove(url) {\n            const node = this.state.map[url];\n            if (node === undefined) {\n                return false;\n            }\n            // Special case if removing the current head.\n            if (this.state.head === url) {\n                // The node is the current head. Special case the removal.\n                if (node.next === null) {\n                    // This is the only node. Reset the cache to be empty.\n                    this.state.head = null;\n                    this.state.tail = null;\n                    this.state.map = {};\n                    this.state.count = 0;\n                    return true;\n                }\n                // There is at least one other node. Make the next node the new head.\n                const next = this.state.map[node.next];\n                next.previous = null;\n                this.state.head = next.url;\n                node.next = null;\n                delete this.state.map[url];\n                this.state.count--;\n                return true;\n            }\n            // The node is not the head, so it has a previous. It may or may not be the tail.\n            // If it is not, then it has a next. First, grab the previous node.\n            const previous = this.state.map[node.previous];\n            // Fix the forward pointer to skip over node and go directly to node.next.\n            previous.next = node.next;\n            // node.next may or may not be set. If it is, fix the back pointer to skip over node.\n            // If it's not set, then this node happened to be the tail, and the tail needs to be\n            // updated to point to the previous node (removing the tail).\n            if (node.next !== null) {\n                // There is a next node, fix its back pointer to skip this node.\n                this.state.map[node.next].previous = node.previous;\n            }\n            else {\n                // There is no next node - the accessed node must be the tail. Move the tail pointer.\n                this.state.tail = node.previous;\n            }\n            node.next = null;\n            node.previous = null;\n            delete this.state.map[url];\n            // Count the removal.\n            this.state.count--;\n            return true;\n        }\n        accessed(url) {\n            // When a URL is accessed, its node needs to be moved to the head of the chain.\n            // This is accomplished in two steps:\n            //\n            // 1) remove the node from its position within the chain.\n            // 2) insert the node as the new head.\n            //\n            // Sometimes, a URL is accessed which has not been seen before. In this case, step 1 can\n            // be skipped completely (which will grow the chain by one). Of course, if the node is\n            // already the head, this whole operation can be skipped.\n            if (this.state.head === url) {\n                // The URL is already in the head position, accessing it is a no-op.\n                return;\n            }\n            // Look up the node in the map, and construct a new entry if it's\n            const node = this.state.map[url] || { url, next: null, previous: null };\n            // Step 1: remove the node from its position within the chain, if it is in the chain.\n            if (this.state.map[url] !== undefined) {\n                this.remove(url);\n            }\n            // Step 2: insert the node at the head of the chain.\n            // First, check if there's an existing head node. If there is, it has previous: null.\n            // Its previous pointer should be set to the node we're inserting.\n            if (this.state.head !== null) {\n                this.state.map[this.state.head].previous = url;\n            }\n            // The next pointer of the node being inserted gets set to the old head, before the head\n            // pointer is updated to this node.\n            node.next = this.state.head;\n            // The new head is the new node.\n            this.state.head = url;\n            // If there is no tail, then this is the first node, and is both the head and the tail.\n            if (this.state.tail === null) {\n                this.state.tail = url;\n            }\n            // Set the node in the map of nodes (if the URL has been seen before, this is a no-op)\n            // and count the insertion.\n            this.state.map[url] = node;\n            this.state.count++;\n        }\n    }\n    /**\n     * A group of cached resources determined by a set of URL patterns which follow a LRU policy\n     * for caching.\n     */\n    class DataGroup {\n        constructor(scope, adapter, config, db, debugHandler, prefix) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.config = config;\n            this.db = db;\n            this.debugHandler = debugHandler;\n            this.prefix = prefix;\n            /**\n             * Tracks the LRU state of resources in this cache.\n             */\n            this._lru = null;\n            this.patterns = this.config.patterns.map(pattern => new RegExp(pattern));\n            this.cache = this.scope.caches.open(`${this.prefix}:dynamic:${this.config.name}:cache`);\n            this.lruTable = this.db.open(`${this.prefix}:dynamic:${this.config.name}:lru`);\n            this.ageTable = this.db.open(`${this.prefix}:dynamic:${this.config.name}:age`);\n        }\n        /**\n         * Lazily initialize/load the LRU chain.\n         */\n        lru() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                if (this._lru === null) {\n                    const table = yield this.lruTable;\n                    try {\n                        this._lru = new LruList(yield table.read('lru'));\n                    }\n                    catch (_a) {\n                        this._lru = new LruList();\n                    }\n                }\n                return this._lru;\n            });\n        }\n        /**\n         * Sync the LRU chain to non-volatile storage.\n         */\n        syncLru() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                if (this._lru === null) {\n                    return;\n                }\n                const table = yield this.lruTable;\n                try {\n                    return table.write('lru', this._lru.state);\n                }\n                catch (err) {\n                    // Writing lru cache table failed. This could be a result of a full storage.\n                    // Continue serving clients as usual.\n                    this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).syncLru()`);\n                    // TODO: Better detect/handle full storage; e.g. using\n                    // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).\n                }\n            });\n        }\n        /**\n         * Process a fetch event and return a `Response` if the resource is covered by this group,\n         * or `null` otherwise.\n         */\n        handleFetch(req, ctx) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Do nothing\n                if (!this.patterns.some(pattern => pattern.test(req.url))) {\n                    return null;\n                }\n                // Lazily initialize the LRU cache.\n                const lru = yield this.lru();\n                // The URL matches this cache. First, check whether this is a mutating request or not.\n                switch (req.method) {\n                    case 'OPTIONS':\n                        // Don't try to cache this - it's non-mutating, but is part of a mutating request.\n                        // Most likely SWs don't even see this, but this guard is here just in case.\n                        return null;\n                    case 'GET':\n                    case 'HEAD':\n                        // Handle the request with whatever strategy was selected.\n                        switch (this.config.strategy) {\n                            case 'freshness':\n                                return this.handleFetchWithFreshness(req, ctx, lru);\n                            case 'performance':\n                                return this.handleFetchWithPerformance(req, ctx, lru);\n                            default:\n                                throw new Error(`Unknown strategy: ${this.config.strategy}`);\n                        }\n                    default:\n                        // This was a mutating request. Assume the cache for this URL is no longer valid.\n                        const wasCached = lru.remove(req.url);\n                        // If there was a cached entry, remove it.\n                        if (wasCached) {\n                            yield this.clearCacheForUrl(req.url);\n                        }\n                        // Sync the LRU chain to non-volatile storage.\n                        yield this.syncLru();\n                        // Finally, fall back on the network.\n                        return this.safeFetch(req);\n                }\n            });\n        }\n        handleFetchWithPerformance(req, ctx, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                let res = null;\n                // Check the cache first. If the resource exists there (and is not expired), the cached\n                // version can be used.\n                const fromCache = yield this.loadFromCache(req, lru);\n                if (fromCache !== null) {\n                    res = fromCache.res;\n                    // Check the age of the resource.\n                    if (this.config.refreshAheadMs !== undefined && fromCache.age >= this.config.refreshAheadMs) {\n                        ctx.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru));\n                    }\n                }\n                if (res !== null) {\n                    return res;\n                }\n                // No match from the cache. Go to the network. Note that this is not an 'await'\n                // call, networkFetch is the actual Promise. This is due to timeout handling.\n                const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);\n                res = yield timeoutFetch;\n                // Since fetch() will always return a response, undefined indicates a timeout.\n                if (res === undefined) {\n                    // The request timed out. Return a Gateway Timeout error.\n                    res = this.adapter.newResponse(null, { status: 504, statusText: 'Gateway Timeout' });\n                    // Cache the network response eventually.\n                    ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru));\n                }\n                else {\n                    // The request completed in time, so cache it inline with the response flow.\n                    yield this.safeCacheResponse(req, res, lru);\n                }\n                return res;\n            });\n        }\n        handleFetchWithFreshness(req, ctx, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Start with a network fetch.\n                const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);\n                let res;\n                // If that fetch errors, treat it as a timed out request.\n                try {\n                    res = yield timeoutFetch;\n                }\n                catch (_a) {\n                    res = undefined;\n                }\n                // If the network fetch times out or errors, fall back on the cache.\n                if (res === undefined) {\n                    ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true));\n                    // Ignore the age, the network response will be cached anyway due to the\n                    // behavior of freshness.\n                    const fromCache = yield this.loadFromCache(req, lru);\n                    res = (fromCache !== null) ? fromCache.res : null;\n                }\n                else {\n                    yield this.safeCacheResponse(req, res, lru, true);\n                }\n                // Either the network fetch didn't time out, or the cache yielded a usable response.\n                // In either case, use it.\n                if (res !== null) {\n                    return res;\n                }\n                // No response in the cache. No choice but to fall back on the full network fetch.\n                return networkFetch;\n            });\n        }\n        networkFetchWithTimeout(req) {\n            // If there is a timeout configured, race a timeout Promise with the network fetch.\n            // Otherwise, just fetch from the network directly.\n            if (this.config.timeoutMs !== undefined) {\n                const networkFetch = this.scope.fetch(req);\n                const safeNetworkFetch = (() => __awaiter$1(this, void 0, void 0, function* () {\n                    try {\n                        return yield networkFetch;\n                    }\n                    catch (_a) {\n                        return this.adapter.newResponse(null, {\n                            status: 504,\n                            statusText: 'Gateway Timeout',\n                        });\n                    }\n                }))();\n                const networkFetchUndefinedError = (() => __awaiter$1(this, void 0, void 0, function* () {\n                    try {\n                        return yield networkFetch;\n                    }\n                    catch (_b) {\n                        return undefined;\n                    }\n                }))();\n                // Construct a Promise<undefined> for the timeout.\n                const timeout = this.adapter.timeout(this.config.timeoutMs);\n                // Race that with the network fetch. This will either be a Response, or `undefined`\n                // in the event that the request errored or timed out.\n                return [Promise.race([networkFetchUndefinedError, timeout]), safeNetworkFetch];\n            }\n            else {\n                const networkFetch = this.safeFetch(req);\n                // Do a plain fetch.\n                return [networkFetch, networkFetch];\n            }\n        }\n        safeCacheResponse(req, resOrPromise, lru, okToCacheOpaque) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                try {\n                    const res = yield resOrPromise;\n                    try {\n                        yield this.cacheResponse(req, res, lru, okToCacheOpaque);\n                    }\n                    catch (err) {\n                        // Saving the API response failed. This could be a result of a full storage.\n                        // Since this data is cached lazily and temporarily, continue serving clients as usual.\n                        this.debugHandler.log(err, `DataGroup(${this.config.name}@${this.config.version}).safeCacheResponse(${req.url}, status: ${res.status})`);\n                        // TODO: Better detect/handle full storage; e.g. using\n                        // [navigator.storage](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorStorage/storage).\n                    }\n                }\n                catch (_a) {\n                    // Request failed\n                    // TODO: Handle this error somehow?\n                }\n            });\n        }\n        loadFromCache(req, lru) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Look for a response in the cache. If one exists, return it.\n                const cache = yield this.cache;\n                let res = yield cache.match(req);\n                if (res !== undefined) {\n                    // A response was found in the cache, but its age is not yet known. Look it up.\n                    try {\n                        const ageTable = yield this.ageTable;\n                        const age = this.adapter.time - (yield ageTable.read(req.url)).age;\n                        // If the response is young enough, use it.\n                        if (age <= this.config.maxAge) {\n                            // Successful match from the cache. Use the response, after marking it as having\n                            // been accessed.\n                            lru.accessed(req.url);\n                            return { res, age };\n                        }\n                        // Otherwise, or if there was an error, assume the response is expired, and evict it.\n                    }\n                    catch (_a) {\n                        // Some error getting the age for the response. Assume it's expired.\n                    }\n                    lru.remove(req.url);\n                    yield this.clearCacheForUrl(req.url);\n                    // TODO: avoid duplicate in event of network timeout, maybe.\n                    yield this.syncLru();\n                }\n                return null;\n            });\n        }\n        /**\n         * Operation for caching the response from the server. This has to happen all\n         * at once, so that the cache and LRU tracking remain in sync. If the network request\n         * completes before the timeout, this logic will be run inline with the response flow.\n         * If the request times out on the server, an error will be returned but the real network\n         * request will still be running in the background, to be cached when it completes.\n         */\n        cacheResponse(req, res, lru, okToCacheOpaque = false) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Only cache successful responses.\n                if (!(res.ok || (okToCacheOpaque && res.type === 'opaque'))) {\n                    return;\n                }\n                // If caching this response would make the cache exceed its maximum size, evict something\n                // first.\n                if (lru.size >= this.config.maxSize) {\n                    // The cache is too big, evict something.\n                    const evictedUrl = lru.pop();\n                    if (evictedUrl !== null) {\n                        yield this.clearCacheForUrl(evictedUrl);\n                    }\n                }\n                // TODO: evaluate for possible race conditions during flaky network periods.\n                // Mark this resource as having been accessed recently. This ensures it won't be evicted\n                // until enough other resources are requested that it falls off the end of the LRU chain.\n                lru.accessed(req.url);\n                // Store the response in the cache (cloning because the browser will consume\n                // the body during the caching operation).\n                yield (yield this.cache).put(req, res.clone());\n                // Store the age of the cache.\n                const ageTable = yield this.ageTable;\n                yield ageTable.write(req.url, { age: this.adapter.time });\n                // Sync the LRU chain to non-volatile storage.\n                yield this.syncLru();\n            });\n        }\n        /**\n         * Delete all of the saved state which this group uses to track resources.\n         */\n        cleanup() {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                // Remove both the cache and the database entries which track LRU stats.\n                yield Promise.all([\n                    this.scope.caches.delete(`${this.prefix}:dynamic:${this.config.name}:cache`),\n                    this.db.delete(`${this.prefix}:dynamic:${this.config.name}:age`),\n                    this.db.delete(`${this.prefix}:dynamic:${this.config.name}:lru`),\n                ]);\n            });\n        }\n        /**\n         * Clear the state of the cache for a particular resource.\n         *\n         * This doesn't remove the resource from the LRU table, that is assumed to have\n         * been done already. This clears the GET and HEAD versions of the request from\n         * the cache itself, as well as the metadata stored in the age table.\n         */\n        clearCacheForUrl(url) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                const [cache, ageTable] = yield Promise.all([this.cache, this.ageTable]);\n                yield Promise.all([\n                    cache.delete(this.adapter.newRequest(url, { method: 'GET' })),\n                    cache.delete(this.adapter.newRequest(url, { method: 'HEAD' })),\n                    ageTable.delete(url),\n                ]);\n            });\n        }\n        safeFetch(req) {\n            return __awaiter$1(this, void 0, void 0, function* () {\n                try {\n                    return this.scope.fetch(req);\n                }\n                catch (_a) {\n                    return this.adapter.newResponse(null, {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const BACKWARDS_COMPATIBILITY_NAVIGATION_URLS = [\n        { positive: true, regex: '^/.*$' },\n        { positive: false, regex: '^/.*\\\\.[^/]*$' },\n        { positive: false, regex: '^/.*__' },\n    ];\n    /**\n     * A specific version of the application, identified by a unique manifest\n     * as determined by its hash.\n     *\n     * Each `AppVersion` can be thought of as a published version of the app\n     * that can be installed as an update to any previously installed versions.\n     */\n    class AppVersion {\n        constructor(scope, adapter, database, idle, debugHandler, manifest, manifestHash) {\n            this.scope = scope;\n            this.adapter = adapter;\n            this.database = database;\n            this.idle = idle;\n            this.debugHandler = debugHandler;\n            this.manifest = manifest;\n            this.manifestHash = manifestHash;\n            /**\n             * A Map of absolute URL paths (/foo.txt) to the known hash of their\n             * contents (if available).\n             */\n            this.hashTable = new Map();\n            /**\n             * Tracks whether the manifest has encountered any inconsistencies.\n             */\n            this._okay = true;\n            // The hashTable within the manifest is an Object - convert it to a Map for easier lookups.\n            Object.keys(this.manifest.hashTable).forEach(url => {\n                this.hashTable.set(url, this.manifest.hashTable[url]);\n            });\n            // Process each `AssetGroup` declared in the manifest. Each declared group gets an `AssetGroup`\n            // instance\n            // created for it, of a type that depends on the configuration mode.\n            this.assetGroups = (manifest.assetGroups || []).map(config => {\n                // Every asset group has a cache that's prefixed by the manifest hash and the name of the\n                // group.\n                const prefix = `${adapter.cacheNamePrefix}:${this.manifestHash}:assets`;\n                // Check the caching mode, which determines when resources will be fetched/updated.\n                switch (config.installMode) {\n                    case 'prefetch':\n                        return new PrefetchAssetGroup(this.scope, this.adapter, this.idle, config, this.hashTable, this.database, prefix);\n                    case 'lazy':\n                        return new LazyAssetGroup(this.scope, this.adapter, this.idle, config, this.hashTable, this.database, prefix);\n                }\n            });\n            // Process each `DataGroup` declared in the manifest.\n            this.dataGroups =\n                (manifest.dataGroups || [])\n                    .map(config => new DataGroup(this.scope, this.adapter, config, this.database, this.debugHandler, `${adapter.cacheNamePrefix}:${config.version}:data`));\n            // This keeps backwards compatibility with app versions without navigation urls.\n            // Fix: https://github.com/angular/angular/issues/27209\n            manifest.navigationUrls = manifest.navigationUrls || BACKWARDS_COMPATIBILITY_NAVIGATION_URLS;\n            // Create `include`/`exclude` RegExps for the `navigationUrls` declared in the manifest.\n            const includeUrls = manifest.navigationUrls.filter(spec => spec.positive);\n            const excludeUrls = manifest.navigationUrls.filter(spec => !spec.positive);\n            this.navigationUrls = {\n                include: includeUrls.map(spec => new RegExp(spec.regex)),\n                exclude: excludeUrls.map(spec => new RegExp(spec.regex)),\n            };\n        }\n        get okay() { return this._okay; }\n        /**\n         * Fully initialize this version of the application. If this Promise resolves successfully, all\n         * required\n         * data has been safely downloaded.\n         */\n        initializeFully(updateFrom) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                try {\n                    // Fully initialize each asset group, in series. Starts with an empty Promise,\n                    // and waits for the previous groups to have been initialized before initializing\n                    // the next one in turn.\n                    yield this.assetGroups.reduce((previous, group) => __awaiter$2(this, void 0, void 0, function* () {\n                        // Wait for the previous groups to complete initialization. If there is a\n                        // failure, this will throw, and each subsequent group will throw, until the\n                        // whole sequence fails.\n                        yield previous;\n                        // Initialize this group.\n                        return group.initializeFully(updateFrom);\n                    }), Promise.resolve());\n                }\n                catch (err) {\n                    this._okay = false;\n                    throw err;\n                }\n            });\n        }\n        handleFetch(req, context) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                // Check the request against each `AssetGroup` in sequence. If an `AssetGroup` can't handle the\n                // request,\n                // it will return `null`. Thus, the first non-null response is the SW's answer to the request.\n                // So reduce\n                // the group list, keeping track of a possible response. If there is one, it gets passed\n                // through, and if\n                // not the next group is consulted to produce a candidate response.\n                const asset = yield this.assetGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    // Wait on the previous potential response. If it's not null, it should just be passed\n                    // through.\n                    const resp = yield potentialResponse;\n                    if (resp !== null) {\n                        return resp;\n                    }\n                    // No response has been found yet. Maybe this group will have one.\n                    return group.handleFetch(req, context);\n                }), Promise.resolve(null));\n                // The result of the above is the asset response, if there is any, or null otherwise. Return the\n                // asset\n                // response if there was one. If not, check with the data caching groups.\n                if (asset !== null) {\n                    return asset;\n                }\n                // Perform the same reduction operation as above, but this time processing\n                // the data caching groups.\n                const data = yield this.dataGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    const resp = yield potentialResponse;\n                    if (resp !== null) {\n                        return resp;\n                    }\n                    return group.handleFetch(req, context);\n                }), Promise.resolve(null));\n                // If the data caching group returned a response, go with it.\n                if (data !== null) {\n                    return data;\n                }\n                // Next, check if this is a navigation request for a route. Detect circular\n                // navigations by checking if the request URL is the same as the index URL.\n                if (req.url !== this.manifest.index && this.isNavigationRequest(req)) {\n                    // This was a navigation request. Re-enter `handleFetch` with a request for\n                    // the URL.\n                    return this.handleFetch(this.adapter.newRequest(this.manifest.index), context);\n                }\n                return null;\n            });\n        }\n        /**\n         * Determine whether the request is a navigation request.\n         * Takes into account: Request mode, `Accept` header, `navigationUrls` patterns.\n         */\n        isNavigationRequest(req) {\n            if (req.mode !== 'navigate') {\n                return false;\n            }\n            if (!this.acceptsTextHtml(req)) {\n                return false;\n            }\n            const urlPrefix = this.scope.registration.scope.replace(/\\/$/, '');\n            const url = req.url.startsWith(urlPrefix) ? req.url.substr(urlPrefix.length) : req.url;\n            const urlWithoutQueryOrHash = url.replace(/[?#].*$/, '');\n            return this.navigationUrls.include.some(regex => regex.test(urlWithoutQueryOrHash)) &&\n                !this.navigationUrls.exclude.some(regex => regex.test(urlWithoutQueryOrHash));\n        }\n        /**\n         * Check this version for a given resource with a particular hash.\n         */\n        lookupResourceWithHash(url, hash) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                // Verify that this version has the requested resource cached. If not,\n                // there's no point in trying.\n                if (!this.hashTable.has(url)) {\n                    return null;\n                }\n                // Next, check whether the resource has the correct hash. If not, any cached\n                // response isn't usable.\n                if (this.hashTable.get(url) !== hash) {\n                    return null;\n                }\n                const cacheState = yield this.lookupResourceWithoutHash(url);\n                return cacheState && cacheState.response;\n            });\n        }\n        /**\n         * Check this version for a given resource regardless of its hash.\n         */\n        lookupResourceWithoutHash(url) {\n            // Limit the search to asset groups, and only scan the cache, don't\n            // load resources from the network.\n            return this.assetGroups.reduce((potentialResponse, group) => __awaiter$2(this, void 0, void 0, function* () {\n                const resp = yield potentialResponse;\n                if (resp !== null) {\n                    return resp;\n                }\n                // fetchFromCacheOnly() avoids any network fetches, and returns the\n                // full set of cache data, not just the Response.\n                return group.fetchFromCacheOnly(url);\n            }), Promise.resolve(null));\n        }\n        /**\n         * List all unhashed resources from all asset groups.\n         */\n        previouslyCachedResources() {\n            return this.assetGroups.reduce((resources, group) => __awaiter$2(this, void 0, void 0, function* () {\n                return (yield resources).concat(yield group.unhashedResources());\n            }), Promise.resolve([]));\n        }\n        recentCacheStatus(url) {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                return this.assetGroups.reduce((current, group) => __awaiter$2(this, void 0, void 0, function* () {\n                    const status = yield current;\n                    if (status === UpdateCacheStatus.CACHED) {\n                        return status;\n                    }\n                    const groupStatus = yield group.cacheStatus(url);\n                    if (groupStatus === UpdateCacheStatus.NOT_CACHED) {\n                        return status;\n                    }\n                    return groupStatus;\n                }), Promise.resolve(UpdateCacheStatus.NOT_CACHED));\n            });\n        }\n        /**\n         * Erase this application version, by cleaning up all the caches.\n         */\n        cleanup() {\n            return __awaiter$2(this, void 0, void 0, function* () {\n                yield Promise.all(this.assetGroups.map(group => group.cleanup()));\n                yield Promise.all(this.dataGroups.map(group => group.cleanup()));\n            });\n        }\n        /**\n         * Get the opaque application data which was provided with the manifest.\n         */\n        get appData() { return this.manifest.appData || null; }\n        /**\n         * Check whether a request accepts `text/html` (based on the `Accept` header).\n         */\n        acceptsTextHtml(req) {\n            const accept = req.headers.get('Accept');\n            if (accept === null) {\n                return false;\n            }\n            const values = accept.split(',');\n            return values.some(value => value.trim().toLowerCase() === 'text/html');\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$3 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const DEBUG_LOG_BUFFER_SIZE = 100;\n    class DebugHandler {\n        constructor(driver, adapter) {\n            this.driver = driver;\n            this.adapter = adapter;\n            // There are two debug log message arrays. debugLogA records new debugging messages.\n            // Once it reaches DEBUG_LOG_BUFFER_SIZE, the array is moved to debugLogB and a new\n            // array is assigned to debugLogA. This ensures that insertion to the debug log is\n            // always O(1) no matter the number of logged messages, and that the total number\n            // of messages in the log never exceeds 2 * DEBUG_LOG_BUFFER_SIZE.\n            this.debugLogA = [];\n            this.debugLogB = [];\n        }\n        handleFetch(req) {\n            return __awaiter$3(this, void 0, void 0, function* () {\n                const [state, versions, idle] = yield Promise.all([\n                    this.driver.debugState(),\n                    this.driver.debugVersions(),\n                    this.driver.debugIdleState(),\n                ]);\n                const msgState = `NGSW Debug Info:\n\nDriver state: ${state.state} (${state.why})\nLatest manifest hash: ${state.latestHash || 'none'}\nLast update check: ${this.since(state.lastUpdateCheck)}`;\n                const msgVersions = versions\n                    .map(version => `=== Version ${version.hash} ===\n\nClients: ${version.clients.join(', ')}`)\n                    .join('\\n\\n');\n                const msgIdle = `=== Idle Task Queue ===\nLast update tick: ${this.since(idle.lastTrigger)}\nLast update run: ${this.since(idle.lastRun)}\nTask queue:\n${idle.queue.map(v => ' * ' + v).join('\\n')}\n\nDebug log:\n${this.formatDebugLog(this.debugLogB)}\n${this.formatDebugLog(this.debugLogA)}\n`;\n                return this.adapter.newResponse(`${msgState}\n\n${msgVersions}\n\n${msgIdle}`, { headers: this.adapter.newHeaders({ 'Content-Type': 'text/plain' }) });\n            });\n        }\n        since(time) {\n            if (time === null) {\n                return 'never';\n            }\n            let age = this.adapter.time - time;\n            const days = Math.floor(age / 86400000);\n            age = age % 86400000;\n            const hours = Math.floor(age / 3600000);\n            age = age % 3600000;\n            const minutes = Math.floor(age / 60000);\n            age = age % 60000;\n            const seconds = Math.floor(age / 1000);\n            const millis = age % 1000;\n            return '' + (days > 0 ? `${days}d` : '') + (hours > 0 ? `${hours}h` : '') +\n                (minutes > 0 ? `${minutes}m` : '') + (seconds > 0 ? `${seconds}s` : '') +\n                (millis > 0 ? `${millis}u` : '');\n        }\n        log(value, context = '') {\n            // Rotate the buffers if debugLogA has grown too large.\n            if (this.debugLogA.length === DEBUG_LOG_BUFFER_SIZE) {\n                this.debugLogB = this.debugLogA;\n                this.debugLogA = [];\n            }\n            // Convert errors to string for logging.\n            if (typeof value !== 'string') {\n                value = this.errorToString(value);\n            }\n            // Log the message.\n            this.debugLogA.push({ value, time: this.adapter.time, context });\n        }\n        errorToString(err) { return `${err.name}(${err.message}, ${err.stack})`; }\n        formatDebugLog(log) {\n            return log.map(entry => `[${this.since(entry.time)}] ${entry.value} ${entry.context}`)\n                .join('\\n');\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$4 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    class IdleScheduler {\n        constructor(adapter, threshold, debug) {\n            this.adapter = adapter;\n            this.threshold = threshold;\n            this.debug = debug;\n            this.queue = [];\n            this.scheduled = null;\n            this.empty = Promise.resolve();\n            this.emptyResolve = null;\n            this.lastTrigger = null;\n            this.lastRun = null;\n        }\n        trigger() {\n            return __awaiter$4(this, void 0, void 0, function* () {\n                this.lastTrigger = this.adapter.time;\n                if (this.queue.length === 0) {\n                    return;\n                }\n                if (this.scheduled !== null) {\n                    this.scheduled.cancel = true;\n                }\n                const scheduled = {\n                    cancel: false,\n                };\n                this.scheduled = scheduled;\n                yield this.adapter.timeout(this.threshold);\n                if (scheduled.cancel) {\n                    return;\n                }\n                this.scheduled = null;\n                yield this.execute();\n            });\n        }\n        execute() {\n            return __awaiter$4(this, void 0, void 0, function* () {\n                this.lastRun = this.adapter.time;\n                while (this.queue.length > 0) {\n                    const queue = this.queue;\n                    this.queue = [];\n                    yield queue.reduce((previous, task) => __awaiter$4(this, void 0, void 0, function* () {\n                        yield previous;\n                        try {\n                            yield task.run();\n                        }\n                        catch (err) {\n                            this.debug.log(err, `while running idle task ${task.desc}`);\n                        }\n                    }), Promise.resolve());\n                }\n                if (this.emptyResolve !== null) {\n                    this.emptyResolve();\n                    this.emptyResolve = null;\n                }\n                this.empty = Promise.resolve();\n            });\n        }\n        schedule(desc, run) {\n            this.queue.push({ desc, run });\n            if (this.emptyResolve === null) {\n                this.empty = new Promise(resolve => { this.emptyResolve = resolve; });\n            }\n        }\n        get size() { return this.queue.length; }\n        get taskDescriptions() { return this.queue.map(task => task.desc); }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    function hashManifest(manifest) {\n        return sha1(JSON.stringify(manifest));\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    function isMsgCheckForUpdates(msg) {\n        return msg.action === 'CHECK_FOR_UPDATES';\n    }\n    function isMsgActivateUpdate(msg) {\n        return msg.action === 'ACTIVATE_UPDATE';\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    var __awaiter$5 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n        return new (P || (P = Promise))(function (resolve, reject) {\n            function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n            function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n            step((generator = generator.apply(thisArg, _arguments || [])).next());\n        });\n    };\n    const IDLE_THRESHOLD = 5000;\n    const SUPPORTED_CONFIG_VERSION = 1;\n    const NOTIFICATION_OPTION_NAMES = [\n        'actions', 'badge', 'body', 'data', 'dir', 'icon', 'image', 'lang', 'renotify',\n        'requireInteraction', 'silent', 'tag', 'timestamp', 'title', 'vibrate'\n    ];\n    var DriverReadyState;\n    (function (DriverReadyState) {\n        // The SW is operating in a normal mode, responding to all traffic.\n        DriverReadyState[DriverReadyState[\"NORMAL\"] = 0] = \"NORMAL\";\n        // The SW does not have a clean installation of the latest version of the app, but older\n        // cached versions are safe to use so long as they don't try to fetch new dependencies.\n        // This is a degraded state.\n        DriverReadyState[DriverReadyState[\"EXISTING_CLIENTS_ONLY\"] = 1] = \"EXISTING_CLIENTS_ONLY\";\n        // The SW has decided that caching is completely unreliable, and is forgoing request\n        // handling until the next restart.\n        DriverReadyState[DriverReadyState[\"SAFE_MODE\"] = 2] = \"SAFE_MODE\";\n    })(DriverReadyState || (DriverReadyState = {}));\n    class Driver {\n        constructor(scope, adapter, db) {\n            // Set up all the event handlers that the SW needs.\n            this.scope = scope;\n            this.adapter = adapter;\n            this.db = db;\n            /**\n             * Tracks the current readiness condition under which the SW is operating. This controls\n             * whether the SW attempts to respond to some or all requests.\n             */\n            this.state = DriverReadyState.NORMAL;\n            this.stateMessage = '(nominal)';\n            /**\n             * Tracks whether the SW is in an initialized state or not. Before initialization,\n             * it's not legal to respond to requests.\n             */\n            this.initialized = null;\n            /**\n             * Maps client IDs to the manifest hash of the application version being used to serve\n             * them. If a client ID is not present here, it has not yet been assigned a version.\n             *\n             * If a ManifestHash appears here, it is also present in the `versions` map below.\n             */\n            this.clientVersionMap = new Map();\n            /**\n             * Maps manifest hashes to instances of `AppVersion` for those manifests.\n             */\n            this.versions = new Map();\n            /**\n             * The latest version fetched from the server.\n             *\n             * Valid after initialization has completed.\n             */\n            this.latestHash = null;\n            this.lastUpdateCheck = null;\n            /**\n             * Whether there is a check for updates currently scheduled due to navigation.\n             */\n            this.scheduledNavUpdateCheck = false;\n            /**\n             * Keep track of whether we have logged an invalid `only-if-cached` request.\n             * (See `.onFetch()` for details.)\n             */\n            this.loggedInvalidOnlyIfCachedRequest = false;\n            // The install event is triggered when the service worker is first installed.\n            this.scope.addEventListener('install', (event) => {\n                // SW code updates are separate from application updates, so code updates are\n                // almost as straightforward as restarting the SW. Because of this, it's always\n                // safe to skip waiting until application tabs are closed, and activate the new\n                // SW version immediately.\n                event.waitUntil(this.scope.skipWaiting());\n            });\n            // The activate event is triggered when this version of the service worker is\n            // first activated.\n            this.scope.addEventListener('activate', (event) => {\n                event.waitUntil((() => __awaiter$5(this, void 0, void 0, function* () {\n                    // As above, it's safe to take over from existing clients immediately, since the new SW\n                    // version will continue to serve the old application.\n                    yield this.scope.clients.claim();\n                    // Once all clients have been taken over, we can delete caches used by old versions of\n                    // `@angular/service-worker`, which are no longer needed. This can happen in the background.\n                    this.idle.schedule('activate: cleanup-old-sw-caches', () => __awaiter$5(this, void 0, void 0, function* () {\n                        try {\n                            yield this.cleanupOldSwCaches();\n                        }\n                        catch (err) {\n                            // Nothing to do - cleanup failed. Just log it.\n                            this.debugger.log(err, 'cleanupOldSwCaches @ activate: cleanup-old-sw-caches');\n                        }\n                    }));\n                }))());\n                // Rather than wait for the first fetch event, which may not arrive until\n                // the next time the application is loaded, the SW takes advantage of the\n                // activation event to schedule initialization. However, if this were run\n                // in the context of the 'activate' event, waitUntil() here would cause fetch\n                // events to block until initialization completed. Thus, the SW does a\n                // postMessage() to itself, to schedule a new event loop iteration with an\n                // entirely separate event context. The SW will be kept alive by waitUntil()\n                // within that separate context while initialization proceeds, while at the\n                // same time the activation event is allowed to resolve and traffic starts\n                // being served.\n                if (this.scope.registration.active !== null) {\n                    this.scope.registration.active.postMessage({ action: 'INITIALIZE' });\n                }\n            });\n            // Handle the fetch, message, and push events.\n            this.scope.addEventListener('fetch', (event) => this.onFetch(event));\n            this.scope.addEventListener('message', (event) => this.onMessage(event));\n            this.scope.addEventListener('push', (event) => this.onPush(event));\n            this.scope.addEventListener('notificationclick', (event) => this.onClick(event));\n            // The debugger generates debug pages in response to debugging requests.\n            this.debugger = new DebugHandler(this, this.adapter);\n            // The IdleScheduler will execute idle tasks after a given delay.\n            this.idle = new IdleScheduler(this.adapter, IDLE_THRESHOLD, this.debugger);\n        }\n        /**\n         * The handler for fetch events.\n         *\n         * This is the transition point between the synchronous event handler and the\n         * asynchronous execution that eventually resolves for respondWith() and waitUntil().\n         */\n        onFetch(event) {\n            const req = event.request;\n            const scopeUrl = this.scope.registration.scope;\n            const requestUrlObj = this.adapter.parseUrl(req.url, scopeUrl);\n            if (req.headers.has('ngsw-bypass') || /[?&]ngsw-bypass(?:[=&]|$)/i.test(requestUrlObj.search)) {\n                return;\n            }\n            // The only thing that is served unconditionally is the debug page.\n            if (requestUrlObj.path === '/ngsw/state') {\n                // Allow the debugger to handle the request, but don't affect SW state in any other way.\n                event.respondWith(this.debugger.handleFetch(req));\n                return;\n            }\n            // If the SW is in a broken state where it's not safe to handle requests at all,\n            // returning causes the request to fall back on the network. This is preferred over\n            // `respondWith(fetch(req))` because the latter still shows in DevTools that the\n            // request was handled by the SW.\n            if (this.state === DriverReadyState.SAFE_MODE) {\n                // Even though the worker is in safe mode, idle tasks still need to happen so\n                // things like update checks, etc. can take place.\n                event.waitUntil(this.idle.trigger());\n                return;\n            }\n            // Although \"passive mixed content\" (like images) only produces a warning without a\n            // ServiceWorker, fetching it via a ServiceWorker results in an error. Let such requests be\n            // handled by the browser, since handling with the ServiceWorker would fail anyway.\n            // See https://github.com/angular/angular/issues/23012#issuecomment-376430187 for more details.\n            if (requestUrlObj.origin.startsWith('http:') && scopeUrl.startsWith('https:')) {\n                // Still, log the incident for debugging purposes.\n                this.debugger.log(`Ignoring passive mixed content request: Driver.fetch(${req.url})`);\n                return;\n            }\n            // When opening DevTools in Chrome, a request is made for the current URL (and possibly related\n            // resources, e.g. scripts) with `cache: 'only-if-cached'` and `mode: 'no-cors'`. These request\n            // will eventually fail, because `only-if-cached` is only allowed to be used with\n            // `mode: 'same-origin'`.\n            // This is likely a bug in Chrome DevTools. Avoid handling such requests.\n            // (See also https://github.com/angular/angular/issues/22362.)\n            // TODO(gkalpak): Remove once no longer necessary (i.e. fixed in Chrome DevTools).\n            if (req.cache === 'only-if-cached' && req.mode !== 'same-origin') {\n                // Log the incident only the first time it happens, to avoid spamming the logs.\n                if (!this.loggedInvalidOnlyIfCachedRequest) {\n                    this.loggedInvalidOnlyIfCachedRequest = true;\n                    this.debugger.log(`Ignoring invalid request: 'only-if-cached' can be set only with 'same-origin' mode`, `Driver.fetch(${req.url}, cache: ${req.cache}, mode: ${req.mode})`);\n                }\n                return;\n            }\n            // Past this point, the SW commits to handling the request itself. This could still\n            // fail (and result in `state` being set to `SAFE_MODE`), but even in that case the\n            // SW will still deliver a response.\n            event.respondWith(this.handleFetch(event));\n        }\n        /**\n         * The handler for message events.\n         */\n        onMessage(event) {\n            // Ignore message events when the SW is in safe mode, for now.\n            if (this.state === DriverReadyState.SAFE_MODE) {\n                return;\n            }\n            // If the message doesn't have the expected signature, ignore it.\n            const data = event.data;\n            if (!data || !data.action) {\n                return;\n            }\n            event.waitUntil((() => __awaiter$5(this, void 0, void 0, function* () {\n                // Initialization is the only event which is sent directly from the SW to itself, and thus\n                // `event.source` is not a `Client`. Handle it here, before the check for `Client` sources.\n                if (data.action === 'INITIALIZE') {\n                    return this.ensureInitialized(event);\n                }\n                // Only messages from true clients are accepted past this point.\n                // This is essentially a typecast.\n                if (!this.adapter.isClient(event.source)) {\n                    return;\n                }\n                // Handle the message and keep the SW alive until it's handled.\n                yield this.ensureInitialized(event);\n                yield this.handleMessage(data, event.source);\n            }))());\n        }\n        onPush(msg) {\n            // Push notifications without data have no effect.\n            if (!msg.data) {\n                return;\n            }\n            // Handle the push and keep the SW alive until it's handled.\n            msg.waitUntil(this.handlePush(msg.data.json()));\n        }\n        onClick(event) {\n            // Handle the click event and keep the SW alive until it's handled.\n            event.waitUntil(this.handleClick(event.notification, event.action));\n        }\n        ensureInitialized(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Since the SW may have just been started, it may or may not have been initialized already.\n                // `this.initialized` will be `null` if initialization has not yet been attempted, or will be a\n                // `Promise` which will resolve (successfully or unsuccessfully) if it has.\n                if (this.initialized !== null) {\n                    return this.initialized;\n                }\n                // Initialization has not yet been attempted, so attempt it. This should only ever happen once\n                // per SW instantiation.\n                try {\n                    this.initialized = this.initialize();\n                    yield this.initialized;\n                }\n                catch (error) {\n                    // If initialization fails, the SW needs to enter a safe state, where it declines to respond\n                    // to network requests.\n                    this.state = DriverReadyState.SAFE_MODE;\n                    this.stateMessage = `Initialization failed due to error: ${errorToString(error)}`;\n                    throw error;\n                }\n                finally {\n                    // Regardless if initialization succeeded, background tasks still need to happen.\n                    event.waitUntil(this.idle.trigger());\n                }\n            });\n        }\n        handleMessage(msg, from) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                if (isMsgCheckForUpdates(msg)) {\n                    const action = (() => __awaiter$5(this, void 0, void 0, function* () { yield this.checkForUpdate(); }))();\n                    yield this.reportStatus(from, action, msg.statusNonce);\n                }\n                else if (isMsgActivateUpdate(msg)) {\n                    yield this.reportStatus(from, this.updateClient(from), msg.statusNonce);\n                }\n            });\n        }\n        handlePush(data) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.broadcast({\n                    type: 'PUSH',\n                    data,\n                });\n                if (!data.notification || !data.notification.title) {\n                    return;\n                }\n                const desc = data.notification;\n                let options = {};\n                NOTIFICATION_OPTION_NAMES.filter(name => desc.hasOwnProperty(name))\n                    .forEach(name => options[name] = desc[name]);\n                yield this.scope.registration.showNotification(desc['title'], options);\n            });\n        }\n        handleClick(notification, action) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                notification.close();\n                const options = {};\n                // The filter uses `name in notification` because the properties are on the prototype so\n                // hasOwnProperty does not work here\n                NOTIFICATION_OPTION_NAMES.filter(name => name in notification)\n                    .forEach(name => options[name] = notification[name]);\n                yield this.broadcast({\n                    type: 'NOTIFICATION_CLICK',\n                    data: { action, notification: options },\n                });\n            });\n        }\n        reportStatus(client, promise, nonce) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const response = { type: 'STATUS', nonce, status: true };\n                try {\n                    yield promise;\n                    client.postMessage(response);\n                }\n                catch (e) {\n                    client.postMessage(Object.assign(Object.assign({}, response), { status: false, error: e.toString() }));\n                }\n            });\n        }\n        updateClient(client) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Figure out which version the client is on. If it's not on the latest,\n                // it needs to be moved.\n                const existing = this.clientVersionMap.get(client.id);\n                if (existing === this.latestHash) {\n                    // Nothing to do, this client is already on the latest version.\n                    return;\n                }\n                // Switch the client over.\n                let previous = undefined;\n                // Look up the application data associated with the existing version. If there\n                // isn't any, fall back on using the hash.\n                if (existing !== undefined) {\n                    const existingVersion = this.versions.get(existing);\n                    previous = this.mergeHashWithAppData(existingVersion.manifest, existing);\n                }\n                // Set the current version used by the client, and sync the mapping to disk.\n                this.clientVersionMap.set(client.id, this.latestHash);\n                yield this.sync();\n                // Notify the client about this activation.\n                const current = this.versions.get(this.latestHash);\n                const notice = {\n                    type: 'UPDATE_ACTIVATED',\n                    previous,\n                    current: this.mergeHashWithAppData(current.manifest, this.latestHash),\n                };\n                client.postMessage(notice);\n            });\n        }\n        handleFetch(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                try {\n                    // Ensure the SW instance has been initialized.\n                    yield this.ensureInitialized(event);\n                }\n                catch (_a) {\n                    // Since the SW is already committed to responding to the currently active request,\n                    // respond with a network fetch.\n                    return this.safeFetch(event.request);\n                }\n                // On navigation requests, check for new updates.\n                if (event.request.mode === 'navigate' && !this.scheduledNavUpdateCheck) {\n                    this.scheduledNavUpdateCheck = true;\n                    this.idle.schedule('check-updates-on-navigation', () => __awaiter$5(this, void 0, void 0, function* () {\n                        this.scheduledNavUpdateCheck = false;\n                        yield this.checkForUpdate();\n                    }));\n                }\n                // Decide which version of the app to use to serve this request. This is asynchronous as in\n                // some cases, a record will need to be written to disk about the assignment that is made.\n                const appVersion = yield this.assignVersion(event);\n                // Bail out\n                if (appVersion === null) {\n                    event.waitUntil(this.idle.trigger());\n                    return this.safeFetch(event.request);\n                }\n                let res = null;\n                try {\n                    // Handle the request. First try the AppVersion. If that doesn't work, fall back on the\n                    // network.\n                    res = yield appVersion.handleFetch(event.request, event);\n                }\n                catch (err) {\n                    if (err.isCritical) {\n                        // Something went wrong with the activation of this version.\n                        yield this.versionFailed(appVersion, err);\n                        event.waitUntil(this.idle.trigger());\n                        return this.safeFetch(event.request);\n                    }\n                    throw err;\n                }\n                // The AppVersion will only return null if the manifest doesn't specify what to do about this\n                // request. In that case, just fall back on the network.\n                if (res === null) {\n                    event.waitUntil(this.idle.trigger());\n                    return this.safeFetch(event.request);\n                }\n                // Trigger the idle scheduling system. The Promise returned by trigger() will resolve after\n                // a specific amount of time has passed. If trigger() hasn't been called again by then (e.g.\n                // on a subsequent request), the idle task queue will be drained and the Promise won't resolve\n                // until that operation is complete as well.\n                event.waitUntil(this.idle.trigger());\n                // The AppVersion returned a usable response, so return it.\n                return res;\n            });\n        }\n        /**\n         * Attempt to quickly reach a state where it's safe to serve responses.\n         */\n        initialize() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // On initialization, all of the serialized state is read out of the 'control'\n                // table. This includes:\n                // - map of hashes to manifests of currently loaded application versions\n                // - map of client IDs to their pinned versions\n                // - record of the most recently fetched manifest hash\n                //\n                // If these values don't exist in the DB, then this is the either the first time\n                // the SW has run or the DB state has been wiped or is inconsistent. In that case,\n                // load a fresh copy of the manifest and reset the state from scratch.\n                // Open up the DB table.\n                const table = yield this.db.open('control');\n                // Attempt to load the needed state from the DB. If this fails, the catch {} block\n                // will populate these variables with freshly constructed values.\n                let manifests, assignments, latest;\n                try {\n                    // Read them from the DB simultaneously.\n                    [manifests, assignments, latest] = yield Promise.all([\n                        table.read('manifests'),\n                        table.read('assignments'),\n                        table.read('latest'),\n                    ]);\n                    // Successfully loaded from saved state. This implies a manifest exists, so\n                    // the update check needs to happen in the background.\n                    this.idle.schedule('init post-load (update, cleanup)', () => __awaiter$5(this, void 0, void 0, function* () {\n                        yield this.checkForUpdate();\n                        try {\n                            yield this.cleanupCaches();\n                        }\n                        catch (err) {\n                            // Nothing to do - cleanup failed. Just log it.\n                            this.debugger.log(err, 'cleanupCaches @ init post-load');\n                        }\n                    }));\n                }\n                catch (_) {\n                    // Something went wrong. Try to start over by fetching a new manifest from the\n                    // server and building up an empty initial state.\n                    const manifest = yield this.fetchLatestManifest();\n                    const hash = hashManifest(manifest);\n                    manifests = {};\n                    manifests[hash] = manifest;\n                    assignments = {};\n                    latest = { latest: hash };\n                    // Save the initial state to the DB.\n                    yield Promise.all([\n                        table.write('manifests', manifests),\n                        table.write('assignments', assignments),\n                        table.write('latest', latest),\n                    ]);\n                }\n                // At this point, either the state has been loaded successfully, or fresh state\n                // with a new copy of the manifest has been produced. At this point, the `Driver`\n                // can have its internals hydrated from the state.\n                // Initialize the `versions` map by setting each hash to a new `AppVersion` instance\n                // for that manifest.\n                Object.keys(manifests).forEach((hash) => {\n                    const manifest = manifests[hash];\n                    // If the manifest is newly initialized, an AppVersion may have already been\n                    // created for it.\n                    if (!this.versions.has(hash)) {\n                        this.versions.set(hash, new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash));\n                    }\n                });\n                // Map each client ID to its associated hash. Along the way, verify that the hash\n                // is still valid for that client ID. It should not be possible for a client to\n                // still be associated with a hash that was since removed from the state.\n                Object.keys(assignments).forEach((clientId) => {\n                    const hash = assignments[clientId];\n                    if (this.versions.has(hash)) {\n                        this.clientVersionMap.set(clientId, hash);\n                    }\n                    else {\n                        this.clientVersionMap.set(clientId, latest.latest);\n                        this.debugger.log(`Unknown version ${hash} mapped for client ${clientId}, using latest instead`, `initialize: map assignments`);\n                    }\n                });\n                // Set the latest version.\n                this.latestHash = latest.latest;\n                // Finally, assert that the latest version is in fact loaded.\n                if (!this.versions.has(latest.latest)) {\n                    throw new Error(`Invariant violated (initialize): latest hash ${latest.latest} has no known manifest`);\n                }\n                // Finally, wait for the scheduling of initialization of all versions in the\n                // manifest. Ordinarily this just schedules the initializations to happen during\n                // the next idle period, but in development mode this might actually wait for the\n                // full initialization.\n                // If any of these initializations fail, versionFailed() will be called either\n                // synchronously or asynchronously to handle the failure and re-map clients.\n                yield Promise.all(Object.keys(manifests).map((hash) => __awaiter$5(this, void 0, void 0, function* () {\n                    try {\n                        // Attempt to schedule or initialize this version. If this operation is\n                        // successful, then initialization either succeeded or was scheduled. If\n                        // it fails, then full initialization was attempted and failed.\n                        yield this.scheduleInitialization(this.versions.get(hash));\n                    }\n                    catch (err) {\n                        this.debugger.log(err, `initialize: schedule init of ${hash}`);\n                        return false;\n                    }\n                })));\n            });\n        }\n        lookupVersionByHash(hash, debugName = 'lookupVersionByHash') {\n            // The version should exist, but check just in case.\n            if (!this.versions.has(hash)) {\n                throw new Error(`Invariant violated (${debugName}): want AppVersion for ${hash} but not loaded`);\n            }\n            return this.versions.get(hash);\n        }\n        /**\n         * Decide which version of the manifest to use for the event.\n         */\n        assignVersion(event) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // First, check whether the event has a (non empty) client ID. If it does, the version may\n                // already be associated.\n                const clientId = event.clientId;\n                if (clientId) {\n                    // Check if there is an assigned client id.\n                    if (this.clientVersionMap.has(clientId)) {\n                        // There is an assignment for this client already.\n                        const hash = this.clientVersionMap.get(clientId);\n                        let appVersion = this.lookupVersionByHash(hash, 'assignVersion');\n                        // Ordinarily, this client would be served from its assigned version. But, if this\n                        // request is a navigation request, this client can be updated to the latest\n                        // version immediately.\n                        if (this.state === DriverReadyState.NORMAL && hash !== this.latestHash &&\n                            appVersion.isNavigationRequest(event.request)) {\n                            // Update this client to the latest version immediately.\n                            if (this.latestHash === null) {\n                                throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                            }\n                            const client = yield this.scope.clients.get(clientId);\n                            yield this.updateClient(client);\n                            appVersion = this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                        }\n                        // TODO: make sure the version is valid.\n                        return appVersion;\n                    }\n                    else {\n                        // This is the first time this client ID has been seen. Whether the SW is in a\n                        // state to handle new clients depends on the current readiness state, so check\n                        // that first.\n                        if (this.state !== DriverReadyState.NORMAL) {\n                            // It's not safe to serve new clients in the current state. It's possible that\n                            // this is an existing client which has not been mapped yet (see below) but\n                            // even if that is the case, it's invalid to make an assignment to a known\n                            // invalid version, even if that assignment was previously implicit. Return\n                            // undefined here to let the caller know that no assignment is possible at\n                            // this time.\n                            return null;\n                        }\n                        // It's safe to handle this request. Two cases apply. Either:\n                        // 1) the browser assigned a client ID at the time of the navigation request, and\n                        //    this is truly the first time seeing this client, or\n                        // 2) a navigation request came previously from the same client, but with no client\n                        //    ID attached. Browsers do this to avoid creating a client under the origin in\n                        //    the event the navigation request is just redirected.\n                        //\n                        // In case 1, the latest version can safely be used.\n                        // In case 2, the latest version can be used, with the assumption that the previous\n                        // navigation request was answered under the same version. This assumption relies\n                        // on the fact that it's unlikely an update will come in between the navigation\n                        // request and requests for subsequent resources on that page.\n                        // First validate the current state.\n                        if (this.latestHash === null) {\n                            throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                        }\n                        // Pin this client ID to the current latest version, indefinitely.\n                        this.clientVersionMap.set(clientId, this.latestHash);\n                        yield this.sync();\n                        // Return the latest `AppVersion`.\n                        return this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                    }\n                }\n                else {\n                    // No client ID was associated with the request. This must be a navigation request\n                    // for a new client. First check that the SW is accepting new clients.\n                    if (this.state !== DriverReadyState.NORMAL) {\n                        return null;\n                    }\n                    // Serve it with the latest version, and assume that the client will actually get\n                    // associated with that version on the next request.\n                    // First validate the current state.\n                    if (this.latestHash === null) {\n                        throw new Error(`Invariant violated (assignVersion): latestHash was null`);\n                    }\n                    // Return the latest `AppVersion`.\n                    return this.lookupVersionByHash(this.latestHash, 'assignVersion');\n                }\n            });\n        }\n        fetchLatestManifest(ignoreOfflineError = false) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const res = yield this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));\n                if (!res.ok) {\n                    if (res.status === 404) {\n                        yield this.deleteAllCaches();\n                        yield this.scope.registration.unregister();\n                    }\n                    else if (res.status === 504 && ignoreOfflineError) {\n                        return null;\n                    }\n                    throw new Error(`Manifest fetch failed! (status: ${res.status})`);\n                }\n                this.lastUpdateCheck = this.adapter.time;\n                return res.json();\n            });\n        }\n        deleteAllCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield (yield this.scope.caches.keys())\n                    .filter(key => key.startsWith(`${this.adapter.cacheNamePrefix}:`))\n                    .reduce((previous, key) => __awaiter$5(this, void 0, void 0, function* () {\n                    yield Promise.all([\n                        previous,\n                        this.scope.caches.delete(key),\n                    ]);\n                }), Promise.resolve());\n            });\n        }\n        /**\n         * Schedule the SW's attempt to reach a fully prefetched state for the given AppVersion\n         * when the SW is not busy and has connectivity. This returns a Promise which must be\n         * awaited, as under some conditions the AppVersion might be initialized immediately.\n         */\n        scheduleInitialization(appVersion) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const initialize = () => __awaiter$5(this, void 0, void 0, function* () {\n                    try {\n                        yield appVersion.initializeFully();\n                    }\n                    catch (err) {\n                        this.debugger.log(err, `initializeFully for ${appVersion.manifestHash}`);\n                        yield this.versionFailed(appVersion, err);\n                    }\n                });\n                // TODO: better logic for detecting localhost.\n                if (this.scope.registration.scope.indexOf('://localhost') > -1) {\n                    return initialize();\n                }\n                this.idle.schedule(`initialization(${appVersion.manifestHash})`, initialize);\n            });\n        }\n        versionFailed(appVersion, err) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // This particular AppVersion is broken. First, find the manifest hash.\n                const broken = Array.from(this.versions.entries()).find(([hash, version]) => version === appVersion);\n                if (broken === undefined) {\n                    // This version is no longer in use anyway, so nobody cares.\n                    return;\n                }\n                const brokenHash = broken[0];\n                const affectedClients = Array.from(this.clientVersionMap.entries())\n                    .filter(([clientId, hash]) => hash === brokenHash)\n                    .map(([clientId]) => clientId);\n                // TODO: notify affected apps.\n                // The action taken depends on whether the broken manifest is the active (latest) or not.\n                // If so, the SW cannot accept new clients, but can continue to service old ones.\n                if (this.latestHash === brokenHash) {\n                    // The latest manifest is broken. This means that new clients are at the mercy of the\n                    // network, but caches continue to be valid for previous versions. This is\n                    // unfortunate but unavoidable.\n                    this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;\n                    this.stateMessage = `Degraded due to: ${errorToString(err)}`;\n                    // Cancel the binding for the affected clients.\n                    affectedClients.forEach(clientId => this.clientVersionMap.delete(clientId));\n                }\n                else {\n                    // The latest version is viable, but this older version isn't. The only\n                    // possible remedy is to stop serving the older version and go to the network.\n                    // Put the affected clients on the latest version.\n                    affectedClients.forEach(clientId => this.clientVersionMap.set(clientId, this.latestHash));\n                }\n                try {\n                    yield this.sync();\n                }\n                catch (err2) {\n                    // We are already in a bad state. No need to make things worse.\n                    // Just log the error and move on.\n                    this.debugger.log(err2, `Driver.versionFailed(${err.message || err})`);\n                }\n            });\n        }\n        setupUpdate(manifest, hash) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, this.debugger, manifest, hash);\n                // Firstly, check if the manifest version is correct.\n                if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {\n                    yield this.deleteAllCaches();\n                    yield this.scope.registration.unregister();\n                    throw new Error(`Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`);\n                }\n                // Cause the new version to become fully initialized. If this fails, then the\n                // version will not be available for use.\n                yield newVersion.initializeFully(this);\n                // Install this as an active version of the app.\n                this.versions.set(hash, newVersion);\n                // Future new clients will use this hash as the latest version.\n                this.latestHash = hash;\n                // If we are in `EXISTING_CLIENTS_ONLY` mode (meaning we didn't have a clean copy of the last\n                // latest version), we can now recover to `NORMAL` mode and start accepting new clients.\n                if (this.state === DriverReadyState.EXISTING_CLIENTS_ONLY) {\n                    this.state = DriverReadyState.NORMAL;\n                    this.stateMessage = '(nominal)';\n                }\n                yield this.sync();\n                yield this.notifyClientsAboutUpdate(newVersion);\n            });\n        }\n        checkForUpdate() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                let hash = '(unknown)';\n                try {\n                    const manifest = yield this.fetchLatestManifest(true);\n                    if (manifest === null) {\n                        // Client or server offline. Unable to check for updates at this time.\n                        // Continue to service clients (existing and new).\n                        this.debugger.log('Check for update aborted. (Client or server offline.)');\n                        return false;\n                    }\n                    hash = hashManifest(manifest);\n                    // Check whether this is really an update.\n                    if (this.versions.has(hash)) {\n                        return false;\n                    }\n                    yield this.setupUpdate(manifest, hash);\n                    return true;\n                }\n                catch (err) {\n                    this.debugger.log(err, `Error occurred while updating to manifest ${hash}`);\n                    this.state = DriverReadyState.EXISTING_CLIENTS_ONLY;\n                    this.stateMessage = `Degraded due to failed initialization: ${errorToString(err)}`;\n                    return false;\n                }\n            });\n        }\n        /**\n         * Synchronize the existing state to the underlying database.\n         */\n        sync() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Open up the DB table.\n                const table = yield this.db.open('control');\n                // Construct a serializable map of hashes to manifests.\n                const manifests = {};\n                this.versions.forEach((version, hash) => { manifests[hash] = version.manifest; });\n                // Construct a serializable map of client ids to version hashes.\n                const assignments = {};\n                this.clientVersionMap.forEach((hash, clientId) => { assignments[clientId] = hash; });\n                // Record the latest entry. Since this is a sync which is necessarily happening after\n                // initialization, latestHash should always be valid.\n                const latest = {\n                    latest: this.latestHash,\n                };\n                // Synchronize all of these.\n                yield Promise.all([\n                    table.write('manifests', manifests),\n                    table.write('assignments', assignments),\n                    table.write('latest', latest),\n                ]);\n            });\n        }\n        cleanupCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Query for all currently active clients, and list the client ids. This may skip\n                // some clients in the browser back-forward cache, but not much can be done about\n                // that.\n                const activeClients = (yield this.scope.clients.matchAll()).map(client => client.id);\n                // A simple list of client ids that the SW has kept track of. Subtracting\n                // activeClients from this list will result in the set of client ids which are\n                // being tracked but are no longer used in the browser, and thus can be cleaned up.\n                const knownClients = Array.from(this.clientVersionMap.keys());\n                // Remove clients in the clientVersionMap that are no longer active.\n                knownClients.filter(id => activeClients.indexOf(id) === -1)\n                    .forEach(id => this.clientVersionMap.delete(id));\n                // Next, determine the set of versions which are still used. All others can be\n                // removed.\n                const usedVersions = new Set();\n                this.clientVersionMap.forEach((version, _) => usedVersions.add(version));\n                // Collect all obsolete versions by filtering out used versions from the set of all versions.\n                const obsoleteVersions = Array.from(this.versions.keys())\n                    .filter(version => !usedVersions.has(version) && version !== this.latestHash);\n                // Remove all the versions which are no longer used.\n                yield obsoleteVersions.reduce((previous, version) => __awaiter$5(this, void 0, void 0, function* () {\n                    // Wait for the other cleanup operations to complete.\n                    yield previous;\n                    // Try to get past the failure of one particular version to clean up (this\n                    // shouldn't happen, but handle it just in case).\n                    try {\n                        // Get ahold of the AppVersion for this particular hash.\n                        const instance = this.versions.get(version);\n                        // Delete it from the canonical map.\n                        this.versions.delete(version);\n                        // Clean it up.\n                        yield instance.cleanup();\n                    }\n                    catch (err) {\n                        // Oh well? Not much that can be done here. These caches will be removed when\n                        // the SW revs its format version, which happens from time to time.\n                        this.debugger.log(err, `cleanupCaches - cleanup ${version}`);\n                    }\n                }), Promise.resolve());\n                // Commit all the changes to the saved state.\n                yield this.sync();\n            });\n        }\n        /**\n         * Delete caches that were used by older versions of `@angular/service-worker` to avoid running\n         * into storage quota limitations imposed by browsers.\n         * (Since at this point the SW has claimed all clients, it is safe to remove those caches.)\n         */\n        cleanupOldSwCaches() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const cacheNames = yield this.scope.caches.keys();\n                const oldSwCacheNames = cacheNames.filter(name => /^ngsw:(?!\\/)/.test(name));\n                yield Promise.all(oldSwCacheNames.map(name => this.scope.caches.delete(name)));\n            });\n        }\n        /**\n         * Determine if a specific version of the given resource is cached anywhere within the SW,\n         * and fetch it if so.\n         */\n        lookupResourceWithHash(url, hash) {\n            return Array\n                // Scan through the set of all cached versions, valid or otherwise. It's safe to do such\n                // lookups even for invalid versions as the cached version of a resource will have the\n                // same hash regardless.\n                .from(this.versions.values())\n                // Reduce the set of versions to a single potential result. At any point along the\n                // reduction, if a response has already been identified, then pass it through, as no\n                // future operation could change the response. If no response has been found yet, keep\n                // checking versions until one is or until all versions have been exhausted.\n                .reduce((prev, version) => __awaiter$5(this, void 0, void 0, function* () {\n                // First, check the previous result. If a non-null result has been found already, just\n                // return it.\n                if ((yield prev) !== null) {\n                    return prev;\n                }\n                // No result has been found yet. Try the next `AppVersion`.\n                return version.lookupResourceWithHash(url, hash);\n            }), Promise.resolve(null));\n        }\n        lookupResourceWithoutHash(url) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const version = this.versions.get(this.latestHash);\n                return version ? version.lookupResourceWithoutHash(url) : null;\n            });\n        }\n        previouslyCachedResources() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const version = this.versions.get(this.latestHash);\n                return version ? version.previouslyCachedResources() : [];\n            });\n        }\n        recentCacheStatus(url) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const version = this.versions.get(this.latestHash);\n                return version ? version.recentCacheStatus(url) : UpdateCacheStatus.NOT_CACHED;\n            });\n        }\n        mergeHashWithAppData(manifest, hash) {\n            return {\n                hash,\n                appData: manifest.appData,\n            };\n        }\n        notifyClientsAboutUpdate(next) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                yield this.initialized;\n                const clients = yield this.scope.clients.matchAll();\n                yield clients.reduce((previous, client) => __awaiter$5(this, void 0, void 0, function* () {\n                    yield previous;\n                    // Firstly, determine which version this client is on.\n                    const version = this.clientVersionMap.get(client.id);\n                    if (version === undefined) {\n                        // Unmapped client - assume it's the latest.\n                        return;\n                    }\n                    if (version === this.latestHash) {\n                        // Client is already on the latest version, no need for a notification.\n                        return;\n                    }\n                    const current = this.versions.get(version);\n                    // Send a notice.\n                    const notice = {\n                        type: 'UPDATE_AVAILABLE',\n                        current: this.mergeHashWithAppData(current.manifest, version),\n                        available: this.mergeHashWithAppData(next.manifest, this.latestHash),\n                    };\n                    client.postMessage(notice);\n                }), Promise.resolve());\n            });\n        }\n        broadcast(msg) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                const clients = yield this.scope.clients.matchAll();\n                clients.forEach(client => { client.postMessage(msg); });\n            });\n        }\n        debugState() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                return {\n                    state: DriverReadyState[this.state],\n                    why: this.stateMessage,\n                    latestHash: this.latestHash,\n                    lastUpdateCheck: this.lastUpdateCheck,\n                };\n            });\n        }\n        debugVersions() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                // Build list of versions.\n                return Array.from(this.versions.keys()).map(hash => {\n                    const version = this.versions.get(hash);\n                    const clients = Array.from(this.clientVersionMap.entries())\n                        .filter(([clientId, version]) => version === hash)\n                        .map(([clientId, version]) => clientId);\n                    return {\n                        hash,\n                        manifest: version.manifest, clients,\n                        status: '',\n                    };\n                });\n            });\n        }\n        debugIdleState() {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                return {\n                    queue: this.idle.taskDescriptions,\n                    lastTrigger: this.idle.lastTrigger,\n                    lastRun: this.idle.lastRun,\n                };\n            });\n        }\n        safeFetch(req) {\n            return __awaiter$5(this, void 0, void 0, function* () {\n                try {\n                    return yield this.scope.fetch(req);\n                }\n                catch (err) {\n                    this.debugger.log(err, `Driver.fetch(${req.url})`);\n                    return this.adapter.newResponse(null, {\n                        status: 504,\n                        statusText: 'Gateway Timeout',\n                    });\n                }\n            });\n        }\n    }\n\n    /**\n     * @license\n     * Copyright Google Inc. All Rights Reserved.\n     *\n     * Use of this source code is governed by an MIT-style license that can be\n     * found in the LICENSE file at https://angular.io/license\n     */\n    const scope = self;\n    const adapter = new Adapter(scope);\n    const driver = new Driver(scope, adapter, new CacheDatabase(scope, adapter));\n\n}());\n"
  },
  {
    "path": "Chapter_12/WorldCities/wwwroot/ngsw.json",
    "content": "{\n  \"configVersion\": 1,\n  \"timestamp\": 1577992220985,\n  \"index\": \"/index.html\",\n  \"assetGroups\": [\n    {\n      \"name\": \"app\",\n      \"installMode\": \"prefetch\",\n      \"updateMode\": \"prefetch\",\n      \"urls\": [\n        \"/index.html\",\n        \"/main-es2015.0dfa6088d7ebe21b943a.js\",\n        \"/main-es5.0dfa6088d7ebe21b943a.js\",\n        \"/manifest.webmanifest\",\n        \"/polyfills-es2015.58725a5910daef768ca8.js\",\n        \"/polyfills-es5.079443d8bcab7d711023.js\",\n        \"/runtime-es2015.e59a6cd8f1b6ab0c3f29.js\",\n        \"/runtime-es5.e59a6cd8f1b6ab0c3f29.js\",\n        \"/styles.519f5f6cb11c0ac03ff3.css\"\n      ],\n      \"patterns\": []\n    },\n    {\n      \"name\": \"assets\",\n      \"installMode\": \"lazy\",\n      \"updateMode\": \"prefetch\",\n      \"urls\": [\n        \"/assets/icons/icon-128x128.png\",\n        \"/assets/icons/icon-144x144.png\",\n        \"/assets/icons/icon-152x152.png\",\n        \"/assets/icons/icon-192x192.png\",\n        \"/assets/icons/icon-384x384.png\",\n        \"/assets/icons/icon-512x512.png\",\n        \"/assets/icons/icon-72x72.png\",\n        \"/assets/icons/icon-96x96.png\"\n      ],\n      \"patterns\": []\n    }\n  ],\n  \"dataGroups\": [],\n  \"hashTable\": {\n    \"/assets/icons/icon-128x128.png\": \"664808390a86b765b38840d2229bb8e77f23315f\",\n    \"/assets/icons/icon-144x144.png\": \"7d2df9f8dfadc3ba692838dc5a05fdda171ffe46\",\n    \"/assets/icons/icon-152x152.png\": \"328dddc298922fee24ca15f6188a8be20b1b5e85\",\n    \"/assets/icons/icon-192x192.png\": \"36caee50fcc2ddeca25acd0a74ccb12a04281dd2\",\n    \"/assets/icons/icon-384x384.png\": \"f3fe7601922932a95550a3fa767efec8eb2c54f2\",\n    \"/assets/icons/icon-512x512.png\": \"8e215e2e14a8fe8ed23ad313003a2da652b0acd9\",\n    \"/assets/icons/icon-72x72.png\": \"cbf6ac39ff640851fd721545fa68595aaf715e50\",\n    \"/assets/icons/icon-96x96.png\": \"745d24bafdf890ad4f63e9c4e69c713212849802\",\n    \"/index.html\": \"b73c833cf32b543e091e8683ded1744c3d7e0c76\",\n    \"/main-es2015.0dfa6088d7ebe21b943a.js\": \"2676c9e57672d9cec97ee29945ab8a60cf7dc916\",\n    \"/main-es5.0dfa6088d7ebe21b943a.js\": \"9332386d541e52f5269f53ca4be1aa8aa4576841\",\n    \"/manifest.webmanifest\": \"43ccf4b7574d245892754a78dcf31cfa4e2ef956\",\n    \"/polyfills-es2015.58725a5910daef768ca8.js\": \"3e29b382a75f751f979baff5606aeb37d3f66c3a\",\n    \"/polyfills-es5.079443d8bcab7d711023.js\": \"f2bfdffc3174ace137ac2516e8682571b6f460b0\",\n    \"/runtime-es2015.e59a6cd8f1b6ab0c3f29.js\": \"a9aafcf49f49145093fc831efd9b8e2f6c71bb9c\",\n    \"/runtime-es5.e59a6cd8f1b6ab0c3f29.js\": \"a9aafcf49f49145093fc831efd9b8e2f6c71bb9c\",\n    \"/styles.519f5f6cb11c0ac03ff3.css\": \"722333c0e596cfbb27969f7ac040dcef942da1ca\"\n  },\n  \"navigationUrls\": [\n    {\n      \"positive\": true,\n      \"regex\": \"^\\\\/.*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*\\\\.[^/]*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*__[^/]*$\"\n    },\n    {\n      \"positive\": false,\n      \"regex\": \"^\\\\/(?:.+\\\\/)?[^/]*__[^/]*\\\\/.*$\"\n    }\n  ]\n}"
  },
  {
    "path": "Chapter_12/WorldCities/wwwroot/safety-worker.js",
    "content": "/**\n * @license\n * Copyright Google Inc. All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.io/license\n */\n\n// tslint:disable:no-console\n\nself.addEventListener('install', event => { self.skipWaiting(); });\n\nself.addEventListener('activate', event => {\n  event.waitUntil(self.clients.claim());\n  self.registration.unregister().then(\n      () => { console.log('NGSW Safety Worker - unregistered old service worker'); });\n});\n"
  },
  {
    "path": "Chapter_12.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29209.62\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"HealthCheck\", \"Chapter_12\\HealthCheck\\HealthCheck.csproj\", \"{55764BDF-9E98-4453-A046-B631089D4B25}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"WorldCities\", \"Chapter_12\\WorldCities\\WorldCities.csproj\", \"{DBA1DEFD-4C35-4E6C-933E-44F5B8BF9567}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{55764BDF-9E98-4453-A046-B631089D4B25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{55764BDF-9E98-4453-A046-B631089D4B25}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{55764BDF-9E98-4453-A046-B631089D4B25}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{55764BDF-9E98-4453-A046-B631089D4B25}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{DBA1DEFD-4C35-4E6C-933E-44F5B8BF9567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{DBA1DEFD-4C35-4E6C-933E-44F5B8BF9567}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{DBA1DEFD-4C35-4E6C-933E-44F5B8BF9567}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{DBA1DEFD-4C35-4E6C-933E-44F5B8BF9567}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {F0EE0686-783C-472C-8B14-22385361AEE1}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Packt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# ASP.NET Core 3 and Angular 9 - Third Edition\n\nThis is the code repository for [ASP.NET Core 3 and Angular 9](https://www.packtpub.com/web-development/asp-net-core-3-and-angular-9-third-edition?utm_source=GitHub&utm_medium=repository), \npublished by [Packt](https://www.packtpub.com/?utm_source=GitHub&utm_medium=repository). It contains all the supporting project files necessary to work through the book from start to finish.\n\n## IMPORTANT NOTE\nIf you experience any issue while running the project while following the book's chapters, please do the following:\n- Get the updated project from GitHub and save it in a separate folder\n- Right-click to the project you want to run (for example `Chapter_01/HealthCheck`) and ***Set as StartUp Project***\n- Execute the `/ClientApp/update-npm.bat` file\n- Launch the project in debug mode\nIn case it works, compare your code with the GitHub files to see what's wrong. Also be sure to check out the book's errata and other useful info regarding the source code from the [Issues page](https://github.com/PacktPublishing/ASP.NET-Core-3-and-Angular-9-Third-Edition/issues).\n\n\n## About the Book\nThis book is a sequel of the best-selling book [ASP.NET Core 2 and Angular 5](https://www.packtpub.com/application-development/aspnet-core-2-and-angular-5?utm_source=GitHub&utm_medium=repository).\n\n\n### Key Features\n* Design, build and deploy a Single Page Application or Progressive Web App with ASP.NET Core and Angular.\n* Adopt a full-stack approach to effectively handle data management, Web APIs, application design, testing, SEO, security and deploy.\n* Turn a Single-Page Application (SPA) into a Progressive Web App (PWA)\n* Publish the finalized app on Windows and Linux servers.\n\n\n### Book Description\nThe ASP.NET Core and Angular book has established itself as a popular choice for learning full-stack development.\nThis book will help you become fluent in both frontend and backend web development by combining the impressive capabilities \nof ASP.NET Core 3.1 and Angular 9 from project setup right through the deployment phase.\n\nThe book will help you use the .NET Core framework and Web API Controllers to implement the backend with API calls \nand server-side routing. Going further, you will learn to build the Data Model with Entity Framework Core and configure it using \neither a local SQL Server instance or cloud-based data stores such as MS Azure. \nThe book will help you handle user input with Angular Reactive Forms and frontend and backend Validators for maximum effect. \nYou will explore the advanced debugging and unit testing features provided by xUnit.NET (.NET Core) and Jasmine/Karma (Angular).\n\nEventually, you will implement various authentication and authorization techniques with the ASP.NET Core Identity system\n and the new IdentityServer, as well as deploy your apps on Windows and Linux Servers using IIS, Kestrel, and NGINX.\n\n\n### What you will learn\n* Implement a Web API interface with ASP.NET Core and consume it with Angular using RxJS Observables\n* Create a Data Model using Entity Framework Core with Code-First and Migrations\n* Setup and configure a SQL DB server using a local instance or a cloud data store on Azure\n* Perform C# and JavaScript debugging using Visual Studio 2019\n* TDD and BDD unit testing using xUnit, Jasmine and Karma\n* Execute authentication and authorization using ASP.NET Identity, IdentityServer4, and Angular API\n* Build Progressive Web Apps and explore Service Workers\n\n\n### Who This Book Is For\nThis book is aimed at seasoned ASP.NET developers who already know about ASP.NET Core and Angular in general, \nbut want to know more about them and/or understand how to blend them together to craft a production-ready \nSingle-Page Application (SPA) or Progressive Web Application (PWA). \n\nHowever, the fully-documented code samples and the step-by-step implementation tutorials \nmake the book easy to understand even for beginners and novice developers.\n\n\n## Instructions and Navigation\nAll of the code is organized into folders. Each folder starts with a number followed by the application name. For example, `Chapter_02`.\n\nThe code will look like the following:\n```\nimport { Component } from \"@angular/core\";\n\n@Component({\n    selector: \"pagenotfound\",\n    templateUrl: \"./pagenotfound.component.html\"\n})\n\nexport class PageNotFoundComponent {\n    title = \"Page not Found\";\n}\n```\n\n## Errata\n* Page 230: This Address bar value- https://localhost:44344/api/Cities/?pageIndex=0pageSize=10\n\n  _should be replaced with_\n\n  https://localhost:44344/api/Cities/?pageIndex=0&pageSize=10  \n\n\n## About the Author\nValerio De Sanctis is a skilled IT professional with more than 15 years of experience in lead programming, \nweb-based development, and project management using ASP.NET, PHP and Java. \nHe held senior positions at a range of financial and insurance companies, most recently serving as Chief Technology Officer, \nChief Security Officer and Chief Operating Officer at a leading after-sales and IT service provider for \ntop-tier life and non-life insurance groups.\n\nDuring the course of his career Valerio De Sanctis helped many private organizations to implement and maintain \n.NET based solutions, working side by side with many IT industry experts and leading several frontend, \nbackend and UX development teams. He designed the architecture and actively oversaw the development \nof a wide number of corporate-level web application projects for high-profile clients, customers and partners \nincluding London Stock Exchange Group, Zurich Insurance Group, Allianz, Generali, Harmonie Mutuelle, \nHonda Motor, FCA Group, Luxottica, ANSA, Saipem, ENI, Enel, Terna, Banzai Media, Virgilio.it, Repubblica.it, and Corriere.it.\n\nHe is an active member of the Stack Exchange Network, providing advice and tips for .NET, JavaScript, \nHTML5 and Web related topics on the StackOverflow, ServerFault, and SuperUser communities. \nMost of his projects and code samples are available under open-source licenses on GitHub, BitBucket, NPM, \nCocoaPods, JQuery Plugin Registry, and WordPress Plugin Repository. \nHe's also a [Microsoft Most Valuable Professional](https://mvp.microsoft.com/en-us/PublicProfile/5003202) (MVP) \nfor Developer Technologies, an annual award that recognizes exceptional technology community leaders worldwide \nwho actively share their high quality, real world expertise with users and Microsoft.\n\nSince 2014 he runs an IT-oriented, web-focused blog at [www.ryadel.com](https://www.ryadel.com/) featuring news, reviews, code samples and guides to help developers and tech enthusiasts from all around the world: he wrote various books on web development, many of which have become best sellers on Amazon with tens of thousands of copies sold worldwide.\n\nYou can reach him on [GitHub](https://github.com/Darkseal), [StackOverflow](https://stackoverflow.com/users/1233379/darkseal) and [LinkedIn](https://www.linkedin.com/in/darkseal/).\n\n\n## Related Products\n* [ASP.NET Core and Angular 2](https://www.packtpub.com/application-development/aspnet-core-and-angular-2?utm_source=GitHub&utm_medium=repository) (first edition)\n* [ASP.NET Core 2 and Angular 5](https://www.packtpub.com/application-development/aspnet-core-2-and-angular-5?utm_source=GitHub&utm_medium=repository) (second edition)\n"
  }
]