From 04b4602c2b5fa87a27f84b66f4d8fa4476d072f0 Mon Sep 17 00:00:00 2001
From: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
Date: Fri, 14 Feb 2020 15:52:23 +0000
Subject: [PATCH] PWA template (#18878)

* Add service worker

* Add manifest

* Bring back BaselineTest.cs

* Add baselines for blazorwasm templates

* Add publishing test for PWA template

* Baseline fixes

* Fix baseline test logic to allow for multi-project outputs

* Remove non-blazorwasm baselines, since this branch now only covers blazorwasm

* Add test for PWA publish output

* Beginning generation of assets manifest

* Generate assets manifest including blazor outputs

* Tweaks

* Write assets manifest in JSON form

* Publish service worker

* Better API

* More resilience

* Better API again

* Make ComputeBlazorAssetsManifestItems public as people will need to customize the list

* Exclude service worker files from assets manifest

* Use web standard format for hash

* Update project template

* In assets manifest, only include items being published

* Renames

* Compute default assets manifest version by combining hashes

* Emit sw manifest in .js form

* Update service worker in project

* Actually isolate browser instances when requested during E2E tests

* E2E test for published PWA operating offline

* Fix SWAM path in template

* Clarify targets
---
 .../GenerateServiceWorkerAssetsManifest.cs    |   82 ++
 .../Blazor/Build/src/targets/All.targets      |    1 +
 .../src/targets/Blazor.MonoRuntime.targets    |    4 +-
 .../ServiceWorkerAssetsManifest.targets       |   98 ++
 src/Components/startvs.cmd                    |    2 +-
 .../BlazorWasm-CSharp.Client.csproj.in        |   13 +
 .../.template.config/dotnetcli.host.json      |    9 +-
 .../.template.config/template.json            |   14 +
 .../.template.config/vs-2017.3.host.json      |    7 +
 .../Client/wwwroot/icon-512.png               |  Bin 0 -> 8953 bytes
 .../Client/wwwroot/index.html                 |    6 +
 .../Client/wwwroot/manifest.json              |   15 +
 .../Client/wwwroot/service-worker.js          |    4 +
 .../wwwroot/service-worker.published.js       |   48 +
 src/ProjectTemplates/test/BaselineTest.cs     |  150 ++
 .../test/BlazorWasmTemplateTest.cs            |   51 +
 .../test/template-baselines.json              | 1282 +----------------
 src/Shared/E2ETesting/BrowserFixture.cs       |   27 +
 18 files changed, 580 insertions(+), 1233 deletions(-)
 create mode 100644 src/Components/Blazor/Build/src/Tasks/GenerateServiceWorkerAssetsManifest.cs
 create mode 100644 src/Components/Blazor/Build/src/targets/ServiceWorkerAssetsManifest.targets
 create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/icon-512.png
 create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/manifest.json
 create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/service-worker.js
 create mode 100644 src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/service-worker.published.js
 create mode 100644 src/ProjectTemplates/test/BaselineTest.cs

diff --git a/src/Components/Blazor/Build/src/Tasks/GenerateServiceWorkerAssetsManifest.cs b/src/Components/Blazor/Build/src/Tasks/GenerateServiceWorkerAssetsManifest.cs
new file mode 100644
index 00000000000..95e20270466
--- /dev/null
+++ b/src/Components/Blazor/Build/src/Tasks/GenerateServiceWorkerAssetsManifest.cs
@@ -0,0 +1,82 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.AspNetCore.Blazor.Build
+{
+    public class GenerateServiceWorkerAssetsManifest : Task
+    {
+        [Required]
+        public string Version { get; set; }
+
+        [Required]
+        public ITaskItem[] AssetsWithHashes { get; set; }
+
+        [Required]
+        public string OutputPath { get; set; }
+
+        public override bool Execute()
+        {
+            using var fileStream = File.Create(OutputPath);
+            WriteFile(fileStream);
+            return true;
+        }
+
+        internal void WriteFile(Stream stream)
+        {
+            var data = new AssetsManifestFile
+            {
+                version = Version,
+                assets = AssetsWithHashes.Select(item => new AssetsManifestFileEntry
+                {
+                    url = item.GetMetadata("AssetUrl"),
+                    hash = $"sha256-{item.GetMetadata("FileHash")}",
+                }).ToArray()
+            };
+
+            using var streamWriter = new StreamWriter(stream, Encoding.UTF8, bufferSize: 50, leaveOpen: true);
+            streamWriter.Write("self.assetsManifest = ");
+            streamWriter.Flush();
+
+            using var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, ownsStream: false, indent: true);
+            new DataContractJsonSerializer(typeof(AssetsManifestFile)).WriteObject(jsonWriter, data);
+            jsonWriter.Flush();
+
+            streamWriter.WriteLine(";");
+        }
+
+#pragma warning disable IDE1006 // Naming Styles
+        public class AssetsManifestFile
+        {
+            /// <summary>
+            /// Gets or sets a version string.
+            /// </summary>
+            public string version { get; set; }
+
+            /// <summary>
+            /// Gets or sets the assets. Keys are URLs; values are base-64-formatted SHA256 content hashes.
+            /// </summary>
+            public AssetsManifestFileEntry[] assets { get; set; }
+        }
+
+        public class AssetsManifestFileEntry
+        {
+            /// <summary>
+            /// Gets or sets the asset URL. Normally this will be relative to the application's base href.
+            /// </summary>
+            public string url { get; set; }
+
+            /// <summary>
+            /// Gets or sets the file content hash. This should be the base-64-formatted SHA256 value.
+            /// </summary>
+            public string hash { get; set; }
+        }
+#pragma warning restore IDE1006 // Naming Styles
+    }
+}
diff --git a/src/Components/Blazor/Build/src/targets/All.targets b/src/Components/Blazor/Build/src/targets/All.targets
index 6c69e85a401..f44a825c235 100644
--- a/src/Components/Blazor/Build/src/targets/All.targets
+++ b/src/Components/Blazor/Build/src/targets/All.targets
@@ -21,6 +21,7 @@
   <Import Project="Blazor.MonoRuntime.targets" />
   <Import Project="Publish.targets" />
   <Import Project="StaticWebAssets.targets" />
+  <Import Project="ServiceWorkerAssetsManifest.targets" />
 
   <Target Name="GenerateBlazorMetadataFile"
     BeforeTargets="GetCopyToOutputDirectoryItems">
diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets
index 911c6167c89..f5fea05473b 100644
--- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets
+++ b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets
@@ -21,7 +21,7 @@
 
   <Target
     Name="_BlazorCopyFilesToOutputDirectory"
-    DependsOnTargets="PrepareBlazorOutputs"
+    DependsOnTargets="PrepareBlazorOutputs;$(_BlazorCopyFilesToOutputDirectoryDependsOn)"
     AfterTargets="CopyFilesToOutputDirectory"
     Condition="'$(OutputType.ToLowerInvariant())'=='exe'">
 
@@ -133,7 +133,7 @@
         ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on.
         Remove assemblies that are inputs to calculating the assembly closure. Instead use the resolved outputs, since it is the minimal set.
        -->
-      <_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" />
+      <_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" Condition="'%(Extension)' == '.dll'" />
       <_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" />
 
       <BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)">
diff --git a/src/Components/Blazor/Build/src/targets/ServiceWorkerAssetsManifest.targets b/src/Components/Blazor/Build/src/targets/ServiceWorkerAssetsManifest.targets
new file mode 100644
index 00000000000..c92c8af17e5
--- /dev/null
+++ b/src/Components/Blazor/Build/src/targets/ServiceWorkerAssetsManifest.targets
@@ -0,0 +1,98 @@
+<Project>
+  
+  <PropertyGroup>
+    <_BlazorCopyFilesToOutputDirectoryDependsOn>
+      $(_BlazorCopyFilesToOutputDirectoryDependsOn);
+      _ComputeServiceWorkerAssetsManifestInputs;
+      _WriteServiceWorkerAssetsManifest;
+    </_BlazorCopyFilesToOutputDirectoryDependsOn>
+  </PropertyGroup>
+
+  <Target Name="_ComputeServiceWorkerAssetsManifestInputs"
+          Condition="'$(ServiceWorkerAssetsManifest)' != ''"
+          DependsOnTargets="PrepareBlazorOutputs">
+
+    <PropertyGroup>
+      <_ServiceWorkerAssetsManifestIntermediateOutputPath>$(BlazorIntermediateOutputPath)serviceworkerassets.js</_ServiceWorkerAssetsManifestIntermediateOutputPath>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <!-- Include _framework/* content -->
+      <ServiceWorkerAssetsManifestItem
+        Include="@(BlazorOutputWithTargetPath)"
+        Condition="$([System.String]::Copy('%(BlazorOutputWithTargetPath.TargetOutputPath)').Replace('\','/').StartsWith('dist/'))">
+        <AssetUrl>$([System.String]::Copy('%(BlazorOutputWithTargetPath.TargetOutputPath)').Replace('\','/').Substring(5))</AssetUrl>
+      </ServiceWorkerAssetsManifestItem>
+
+      <!-- Include content from wwwroot -->
+      <ServiceWorkerAssetsManifestItem
+        Include="@(ContentWithTargetPath)"
+        Condition="
+          ('%(ContentWithTargetPath.CopyToPublishDirectory)' == 'Always' OR '%(ContentWithTargetPath.CopyToPublishDirectory)' == 'PreserveNewest')
+          AND $([System.String]::Copy('%(ContentWithTargetPath.TargetPath)').Replace('\','/').StartsWith('wwwroot/'))">
+        <AssetUrl>$([System.String]::Copy('%(ContentWithTargetPath.TargetPath)').Replace('\','/').Substring(8))</AssetUrl>
+      </ServiceWorkerAssetsManifestItem>
+
+      <!-- Include SWA from references -->
+      <ServiceWorkerAssetsManifestItem
+        Include="@(StaticWebAsset)"
+        Condition="'%(StaticWebAsset.SourceType)' != ''">
+        <AssetUrl>%(StaticWebAsset.BasePath)/%(StaticWebAsset.RelativePath)</AssetUrl>
+      </ServiceWorkerAssetsManifestItem>
+    </ItemGroup>
+
+  </Target>
+
+  <UsingTask TaskName="GenerateServiceWorkerAssetsManifest" AssemblyFile="$(BlazorTasksPath)" />
+
+  <Target Name="_WriteServiceWorkerAssetsManifest"
+          Inputs="@(ServiceWorkerAssetsManifestItem)"
+          Outputs="$(_ServiceWorkerAssetsManifestIntermediateOutputPath)"
+          DependsOnTargets="_ComputeServiceWorkerAssetsManifestFileHashes; _ComputeDefaultServiceWorkerAssetsManifestVersion">
+
+    <GenerateServiceWorkerAssetsManifest
+      Version="$(ServiceWorkerAssetsManifestVersion)"
+      AssetsWithHashes="@(_ServiceWorkerAssetsManifestItemWithHash)"
+      OutputPath="$(_ServiceWorkerAssetsManifestIntermediateOutputPath)" />
+
+    <ItemGroup>
+      <BlazorOutputWithTargetPath
+        Include="$(_ServiceWorkerAssetsManifestIntermediateOutputPath)"
+        TargetOutputPath="$(BaseBlazorDistPath)$(ServiceWorkerAssetsManifest)" />
+      
+      <FileWrites Include="$(_ServiceWorkerAssetsManifestIntermediateOutputPath)" />
+    </ItemGroup>
+
+  </Target>
+
+  <Target Name="_ComputeServiceWorkerAssetsManifestFileHashes">
+    <GetFileHash Files="@(ServiceWorkerAssetsManifestItem)" Algorithm="SHA256" HashEncoding="base64">
+      <Output TaskParameter="Items" ItemName="_ServiceWorkerAssetsManifestItemWithHash" />
+    </GetFileHash>
+  </Target>
+
+  <!--
+    If no ServiceWorkerAssetsManifestVersion was specified, we compute a default value by combining all the asset hashes.
+    This is useful because then clients will only have to repopulate caches if the contents have changed.
+  -->
+  <Target Name="_ComputeDefaultServiceWorkerAssetsManifestVersion"
+          Condition="'$(ServiceWorkerAssetsManifestVersion)' == ''">
+    <PropertyGroup>
+      <_CombinedHashIntermediatePath>$(BlazorIntermediateOutputPath)serviceworkerhashes.txt</_CombinedHashIntermediatePath>
+    </PropertyGroup>
+
+    <WriteLinesToFile
+      File="$(_CombinedHashIntermediatePath)"
+      Lines="@(_ServiceWorkerAssetsManifestItemWithHash->'%(FileHash)')"
+      Overwrite="true" />
+
+    <GetFileHash Files="$(_CombinedHashIntermediatePath)" Algorithm="SHA256" HashEncoding="base64">
+      <Output TaskParameter="Items" ItemName="_ServiceWorkerAssetsManifestCombinedHash" />
+    </GetFileHash>
+
+    <PropertyGroup>
+      <ServiceWorkerAssetsManifestVersion>$([System.String]::Copy('%(_ServiceWorkerAssetsManifestCombinedHash.FileHash)').Substring(0, 8))</ServiceWorkerAssetsManifestVersion>
+    </PropertyGroup>
+  </Target>
+  
+</Project>
diff --git a/src/Components/startvs.cmd b/src/Components/startvs.cmd
index 1eb52561227..962cc73686e 100644
--- a/src/Components/startvs.cmd
+++ b/src/Components/startvs.cmd
@@ -1,3 +1,3 @@
 @ECHO OFF
 
-%~dp0..\..\startvs.cmd %~dp0Components.sln
+%~dp0..\..\startvs.cmd %~dp0Blazor.sln
diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Client.csproj.in b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Client.csproj.in
index 70927b08e33..d1a3d37097a 100644
--- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Client.csproj.in
+++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Client.csproj.in
@@ -3,6 +3,7 @@
   <PropertyGroup>
     <TargetFramework>netstandard2.1</TargetFramework>
     <RazorLangVersion>3.0</RazorLangVersion>
+    <ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
   </PropertyGroup>
 
   <ItemGroup>
@@ -11,10 +12,22 @@
     <PackageReference Include="Microsoft.AspNetCore.Blazor.DevServer" Version="${MicrosoftAspNetCoreBlazorDevServerPackageVersion}" PrivateAssets="all" />
     <PackageReference Include="Microsoft.AspNetCore.Blazor.HttpClient" Version="${MicrosoftAspNetCoreBlazorHttpClientPackageVersion}" />
   </ItemGroup>
+
   <!--#if Hosted -->
   <ItemGroup>
     <ProjectReference Include="..\Shared\BlazorWasm-CSharp.Shared.csproj" />
   </ItemGroup>
+
   <!--#endif -->
+  <!--#if PWA -->
+  <ItemGroup>
+    <!-- When publishing, swap service-worker.published.js in place of service-worker.js -->
+    <Content Update="wwwroot\service-worker*.js" CopyToPublishDirectory="false" />
+    <ContentWithTargetPath Include="wwwroot\service-worker.published.js">
+      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
+      <TargetPath>wwwroot\service-worker.js</TargetPath>
+    </ContentWithTargetPath>
+  </ItemGroup>
 
+  <!--#endif -->
 </Project>
diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/dotnetcli.host.json
index 4e89e1d2dca..be1ce91d661 100644
--- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/dotnetcli.host.json
+++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/dotnetcli.host.json
@@ -5,9 +5,12 @@
       "longName": "no-restore",
       "shortName": ""
     },
-      "Hosted": {
-        "longName": "hosted"
-      },
+    "Hosted": {
+      "longName": "hosted"
+    },
+    "PWA": {
+      "longName": "pwa"
+    },
     "Framework": {
       "longName": "framework"
     }
diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/template.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/template.json
index 1c18d08504a..73de9b1a06d 100644
--- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/template.json
+++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/template.json
@@ -74,6 +74,14 @@
           "exclude": [
             "*.sln"
           ]
+        },
+        {
+          "condition": "(!PWA)",
+          "exclude": [
+            "Client/wwwroot/service-worker*.js",
+            "Client/wwwroot/manifest.json",
+            "Client/wwwroot/icon-512.png"
+          ]
         }
       ]
     }
@@ -147,6 +155,12 @@
         "fallbackVariableName": "HttpsPortGenerated"
       },
       "replaces": "44300"
+    },
+    "PWA": {
+      "type": "parameter",
+      "datatype": "bool",
+      "defaultValue": "false",
+      "description": "If specified, produces a Progressive Web Application (PWA) supporting installation and offline use."
     }
   },
   "tags": {
diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/vs-2017.3.host.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/vs-2017.3.host.json
index 5cb50d10a51..b87413a03ff 100644
--- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/vs-2017.3.host.json
+++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/vs-2017.3.host.json
@@ -25,6 +25,13 @@
         "text": "ASP.NET Core _hosted"
       },
       "isVisible": "true"
+    },
+    {
+      "id": "PWA",
+      "name": {
+        "text": "_Progressive Web Application"
+      },
+      "isVisible": "true"
     }
   ]
 }
diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/icon-512.png b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/icon-512.png
new file mode 100644
index 0000000000000000000000000000000000000000..370c2082b633c88ad9b4a0ee617933ab847bdaab
GIT binary patch
literal 8953
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

literal 0
HcmV?d00001

diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/index.html b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/index.html
index 7c402d80737..124cae83165 100644
--- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/index.html
+++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/index.html
@@ -8,6 +8,9 @@
     <base href="/" />
     <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
     <link href="css/site.css" rel="stylesheet" />
+    <!--#if PWA -->
+    <link href="manifest.json" rel="manifest" />
+    <!--#endif -->
 </head>
 
 <body>
@@ -19,6 +22,9 @@
         <a class="dismiss">🗙</a>
     </div>
     <script src="_framework/blazor.webassembly.js"></script>
+    <!--#if PWA -->
+    <script>navigator.serviceWorker.register('service-worker.js');</script>
+    <!--#endif -->
 </body>
 
 </html>
diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/manifest.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/manifest.json
new file mode 100644
index 00000000000..d986d96b028
--- /dev/null
+++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/manifest.json
@@ -0,0 +1,15 @@
+{
+  "name": "BlazorWasm-CSharp",
+  "short_name": "BlazorWasm-CSharp",
+  "start_url": "/",
+  "display": "standalone",
+  "background_color": "#ffffff",
+  "theme_color": "#03173d",
+  "icons": [
+    {
+      "src": "icon-512.png",
+      "type": "image/png",
+      "sizes": "512x512"
+    }
+  ]
+}
diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/service-worker.js b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/service-worker.js
new file mode 100644
index 00000000000..fe614daee09
--- /dev/null
+++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/service-worker.js
@@ -0,0 +1,4 @@
+// In development, always fetch from the network and do not enable offline support.
+// This is because caching would make development more difficult (changes would not
+// be reflected on the first load after each change).
+self.addEventListener('fetch', () => { });
diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/service-worker.published.js b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/service-worker.published.js
new file mode 100644
index 00000000000..220bba532ea
--- /dev/null
+++ b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/service-worker.published.js
@@ -0,0 +1,48 @@
+// Caution! Be sure you understand the caveats before publishing an application with
+// offline support. See https://aka.ms/blazor-offline-considerations
+
+self.importScripts('./service-worker-assets.js');
+self.addEventListener('install', event => event.waitUntil(onInstall(event)));
+self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
+self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
+
+const cacheNamePrefix = 'offline-cache-';
+const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
+const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/ ];
+const offlineAssetsExclude = [ /^service-worker\.js$/ ];
+
+async function onInstall(event) {
+    console.info('Service worker: Install');
+
+    // Fetch and cache all matching items from the assets manifest
+    const assetsRequests = self.assetsManifest.assets
+        .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
+        .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
+        .map(asset => new Request(asset.url, { integrity: asset.hash }));
+    await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
+}
+
+async function onActivate(event) {
+    console.info('Service worker: Activate');
+
+    // Delete unused caches
+    const cacheKeys = await caches.keys();
+    await Promise.all(cacheKeys
+        .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
+        .map(key => caches.delete(key)));
+}
+
+async function onFetch(event) {
+    let cachedResponse = null;
+    if (event.request.method === 'GET') {
+        // For all navigation requests, try to serve index.html from cache
+        // If you need some URLs to be server-rendered, edit the following check to exclude those URLs
+        const shouldServeIndexHtml = event.request.mode === 'navigate';
+
+        const request = shouldServeIndexHtml ? 'index.html' : event.request;
+        const cache = await caches.open(cacheName);
+        cachedResponse = await cache.match(request);
+    }
+
+    return cachedResponse || fetch(event.request);
+}
diff --git a/src/ProjectTemplates/test/BaselineTest.cs b/src/ProjectTemplates/test/BaselineTest.cs
new file mode 100644
index 00000000000..3da015f6918
--- /dev/null
+++ b/src/ProjectTemplates/test/BaselineTest.cs
@@ -0,0 +1,150 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Templates.Test.Helpers;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Templates.Test
+{
+    public class BaselineTest
+    {
+        private static readonly Regex TemplateNameRegex = new Regex(
+            "new (?<template>[a-zA-Z]+)",
+            RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline,
+            TimeSpan.FromSeconds(1));
+
+        private static readonly Regex AuthenticationOptionRegex = new Regex(
+            "-au (?<auth>[a-zA-Z]+)",
+            RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline,
+            TimeSpan.FromSeconds(1));
+
+        private static readonly Regex LanguageRegex = new Regex(
+            "--language (?<language>\\w+)",
+            RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline,
+            TimeSpan.FromSeconds(1));
+
+        public BaselineTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output)
+        {
+            ProjectFactory = projectFactory;
+            Output = output;
+        }
+
+        public Project Project { get; set; }
+
+        public static TheoryData<string, string[]> TemplateBaselines
+        {
+            get
+            {
+                using (var stream = typeof(BaselineTest).Assembly.GetManifestResourceStream("ProjectTemplates.Tests.template-baselines.json"))
+                {
+                    using (var jsonReader = new JsonTextReader(new StreamReader(stream)))
+                    {
+                        var baseline = JObject.Load(jsonReader);
+                        var data = new TheoryData<string, string[]>();
+                        foreach (var template in baseline)
+                        {
+                            foreach (var authOption in (JObject)template.Value)
+                            {
+                                data.Add(
+                                    (string)authOption.Value["Arguments"],
+                                    ((JArray)authOption.Value["Files"]).Select(s => (string)s).ToArray());
+                            }
+                        }
+
+                        return data;
+                    }
+                }
+            }
+        }
+
+        public ProjectFactoryFixture ProjectFactory { get; }
+        public ITestOutputHelper Output { get; }
+
+        [Theory]
+        [MemberData(nameof(TemplateBaselines))]
+        public async Task Template_Produces_The_Right_Set_Of_FilesAsync(string arguments, string[] expectedFiles)
+        {
+            Project = await ProjectFactory.GetOrCreateProject("baseline" + SanitizeArgs(arguments), Output);
+            var createResult = await Project.RunDotNetNewRawAsync(arguments);
+            Assert.True(createResult.ExitCode == 0, createResult.GetFormattedOutput());
+
+            foreach (var file in expectedFiles)
+            {
+                AssertFileExists(Project.TemplateOutputDir, file, shouldExist: true);
+            }
+
+            var filesInFolder = Directory.EnumerateFiles(Project.TemplateOutputDir, "*", SearchOption.AllDirectories);
+            foreach (var file in filesInFolder)
+            {
+                var relativePath = file.Replace(Project.TemplateOutputDir, "").Replace("\\", "/").Trim('/');
+                if (relativePath.EndsWith(".csproj", StringComparison.Ordinal) ||
+                    relativePath.EndsWith(".fsproj", StringComparison.Ordinal) ||
+                    relativePath.EndsWith(".props", StringComparison.Ordinal) ||
+                    relativePath.EndsWith(".sln", StringComparison.Ordinal) ||
+                    relativePath.EndsWith(".targets", StringComparison.Ordinal) ||
+                    relativePath.StartsWith("bin/", StringComparison.Ordinal) ||
+                    relativePath.StartsWith("obj/", StringComparison.Ordinal) ||
+                    relativePath.Contains("/bin/", StringComparison.Ordinal) ||
+                    relativePath.Contains("/obj/", StringComparison.Ordinal))
+                {
+                    continue;
+                }
+                Assert.Contains(relativePath, expectedFiles);
+            }
+        }
+
+        private string SanitizeArgs(string arguments)
+        {
+            var text = TemplateNameRegex.Match(arguments)
+                .Groups.TryGetValue("template", out var template) ? template.Value : "";
+
+            text += AuthenticationOptionRegex.Match(arguments)
+                .Groups.TryGetValue("auth", out var auth) ? auth.Value : "";
+
+            text += arguments.Contains("--uld") ? "uld" : "";
+
+            text += LanguageRegex.Match(arguments)
+                .Groups.TryGetValue("language", out var language) ? language.Value.Replace("#", "Sharp") : "";
+
+            if (arguments.Contains("--support-pages-and-views true"))
+            {
+                text += "supportpagesandviewstrue";
+            }
+
+            if (arguments.Contains("-ho"))
+            {
+                text += "hosted";
+            }
+
+            if (arguments.Contains("--pwa"))
+            {
+                text += "pwa";
+            }
+
+            return text;
+        }
+
+        private void AssertFileExists(string basePath, string path, bool shouldExist)
+        {
+            var fullPath = Path.Combine(basePath, path);
+            var doesExist = File.Exists(fullPath);
+
+            if (shouldExist)
+            {
+                Assert.True(doesExist, "Expected file to exist, but it doesn't: " + path);
+            }
+            else
+            {
+                Assert.False(doesExist, "Expected file not to exist, but it does: " + path);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs
index 41cb82ac21e..0ac806a6858 100644
--- a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs
+++ b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs
@@ -1,6 +1,7 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
+using System;
 using System.IO;
 using System.Net;
 using System.Threading;
@@ -24,6 +25,11 @@ namespace Templates.Test
 
         public ProjectFactoryFixture ProjectFactory { get; set; }
 
+        public override Task InitializeAsync()
+        {
+            return InitializeAsync(isolationContext: Guid.NewGuid().ToString());
+        }
+
         [Fact]
         public async Task BlazorWasmStandaloneTemplate_Works()
         {
@@ -90,6 +96,51 @@ namespace Templates.Test
             }
         }
 
+        [Fact]
+        public async Task BlazorWasmPwaTemplate_Works()
+        {
+            var project = await ProjectFactory.GetOrCreateProject("blazorpwa", Output);
+            project.TargetFramework = "netstandard2.1";
+
+            var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--pwa" });
+            Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult));
+
+            var publishResult = await project.RunDotNetPublishAsync();
+            Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult));
+
+            var buildResult = await project.RunDotNetBuildAsync();
+            Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult));
+
+            await BuildAndRunTest(project.ProjectName, project);
+
+            var publishDir = Path.Combine(project.TemplatePublishDir, project.ProjectName, "dist");
+            AspNetProcess.EnsureDevelopmentCertificates();
+
+            // When publishing the PWA template, we generate an assets manifest
+            // and move service-worker.published.js to overwrite service-worker.js
+            Assert.False(File.Exists(Path.Combine(publishDir, "service-worker.published.js")), "service-worker.published.js should not be published");
+            Assert.True(File.Exists(Path.Combine(publishDir, "service-worker.js")), "service-worker.js should be published");
+            Assert.True(File.Exists(Path.Combine(publishDir, "service-worker-assets.js")), "service-worker-assets.js should be published");
+
+            // Todo: Use dynamic port assignment: https://github.com/natemcmaster/dotnet-serve/pull/40/files
+            var listeningUri = "https://localhost:8080";
+
+            Output.WriteLine("Running dotnet serve on published output...");
+            using (var serveProcess = ProcessEx.Run(Output, publishDir, DotNetMuxer.MuxerPathOrDefault(), "serve -S"))
+            {
+                Output.WriteLine($"Opening browser at {listeningUri}...");
+                Browser.Navigate().GoToUrl(listeningUri);
+                TestBasicNavigation(project.ProjectName);
+            }
+
+            // The PWA template supports offline use. By now, the browser should have cached everything it needs,
+            // so we can continue working even without the server.
+            Browser.Navigate().GoToUrl("about:blank"); // Be sure we're really reloading
+            Output.WriteLine($"Opening browser without corresponding server at {listeningUri}...");
+            Browser.Navigate().GoToUrl(listeningUri);
+            TestBasicNavigation(project.ProjectName);
+        }
+
         protected async Task BuildAndRunTest(string appName, Project project)
         {
             using var aspNetProcess = project.StartBuiltProjectAsync();
diff --git a/src/ProjectTemplates/test/template-baselines.json b/src/ProjectTemplates/test/template-baselines.json
index d934d8b5456..aac6a24eec7 100644
--- a/src/ProjectTemplates/test/template-baselines.json
+++ b/src/ProjectTemplates/test/template-baselines.json
@@ -1,1273 +1,101 @@
 {
-  "razor": {
-    "Individual": {
-      "Template": "razor",
-      "Arguments": "new razor -au Individual",
-      "Files": [
-        "app.db",
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Areas/Identity/Pages/_ViewStart.cshtml",
-        "Data/ApplicationDbContext.cs",
-        "Data/Migrations/00000000000000_CreateIdentitySchema.cs",
-        "Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs",
-        "Data/Migrations/ApplicationDbContextModelSnapshot.cs",
-        "Pages/Error.cshtml",
-        "Pages/Error.cshtml.cs",
-        "Pages/Index.cshtml",
-        "Pages/Index.cshtml.cs",
-        "Pages/Privacy.cshtml",
-        "Pages/Privacy.cshtml.cs",
-        "Pages/_ViewImports.cshtml",
-        "Pages/_ViewStart.cshtml",
-        "Pages/Shared/_Layout.cshtml",
-        "Pages/Shared/_LoginPartial.cshtml",
-        "Pages/Shared/_ValidationScriptsPartial.cshtml",
-        "Properties/launchSettings.json",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "Individual"
-    },
-    "Windows": {
-      "Template": "razor",
-      "Arguments": "new razor -au Windows",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Pages/Error.cshtml",
-        "Pages/Error.cshtml.cs",
-        "Pages/Index.cshtml",
-        "Pages/Index.cshtml.cs",
-        "Pages/Privacy.cshtml",
-        "Pages/Privacy.cshtml.cs",
-        "Pages/_ViewImports.cshtml",
-        "Pages/_ViewStart.cshtml",
-        "Pages/Shared/_Layout.cshtml",
-        "Pages/Shared/_ValidationScriptsPartial.cshtml",
-        "Properties/launchSettings.json",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "Windows"
-    },
-    "SingleOrg": {
-      "Template": "razor",
-      "Arguments": "new razor -au SingleOrg --aad-instance https://login.microsoftonline.com/ --domain fake-aad-domain.onmicrosoft.com --client-id db33c356-12cc-4953-9167-00ad56c2e8b2 --tenant-id 7e511586-66ec-4108-bc9c-a68dee0dc2aa",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Pages/Error.cshtml",
-        "Pages/Error.cshtml.cs",
-        "Pages/Index.cshtml",
-        "Pages/Index.cshtml.cs",
-        "Pages/Privacy.cshtml",
-        "Pages/Privacy.cshtml.cs",
-        "Pages/_ViewImports.cshtml",
-        "Pages/_ViewStart.cshtml",
-        "Pages/Shared/_Layout.cshtml",
-        "Pages/Shared/_LoginPartial.cshtml",
-        "Pages/Shared/_ValidationScriptsPartial.cshtml",
-        "Properties/launchSettings.json",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "SingleOrg"
-    },
-    "IndividualB2C": {
-      "Template": "razor",
-      "Arguments": "new razor -au IndividualB2C --aad-b2c-instance https://login.microsoftonline.com/tfp/ --domain fake-b2c-domain.onmicrosoft.com --client-id 64f31f76-2750-49e4-aab9-f5de105b5172 -ssp B2C_1_SiUpIn -rp B2C_1_SSPR -ep B2C_1_SiPe",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Pages/Error.cshtml",
-        "Pages/Error.cshtml.cs",
-        "Pages/Index.cshtml",
-        "Pages/Index.cshtml.cs",
-        "Pages/Privacy.cshtml",
-        "Pages/Privacy.cshtml.cs",
-        "Pages/_ViewImports.cshtml",
-        "Pages/_ViewStart.cshtml",
-        "Pages/Shared/_Layout.cshtml",
-        "Pages/Shared/_LoginPartial.cshtml",
-        "Pages/Shared/_ValidationScriptsPartial.cshtml",
-        "Properties/launchSettings.json",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "IndividualB2C"
-    },
+  "blazorwasm": {
     "None": {
-      "Template": "razor",
-      "Arguments": "new razor -au None",
+      "Template": "blazorwasm",
+      "Arguments": "new blazorwasm",
       "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Pages/Error.cshtml",
-        "Pages/Error.cshtml.cs",
-        "Pages/Index.cshtml",
-        "Pages/Index.cshtml.cs",
-        "Pages/Privacy.cshtml",
-        "Pages/Privacy.cshtml.cs",
-        "Pages/_ViewImports.cshtml",
-        "Pages/_ViewStart.cshtml",
-        "Pages/Shared/_Layout.cshtml",
-        "Pages/Shared/_ValidationScriptsPartial.cshtml",
-        "Properties/launchSettings.json",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "None"
-    },
-    "MultiOrg": {
-      "Template": "razor",
-      "Arguments": "new razor -au MultiOrg --aad-instance https://login.microsoftonline.com/ --client-id bac81cbb-9fab-4a4e-9521-be07290c3a51",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Pages/Error.cshtml",
-        "Pages/Error.cshtml.cs",
-        "Pages/Index.cshtml",
-        "Pages/Index.cshtml.cs",
-        "Pages/Privacy.cshtml",
-        "Pages/Privacy.cshtml.cs",
-        "Pages/_ViewImports.cshtml",
-        "Pages/_ViewStart.cshtml",
-        "Pages/Shared/_Layout.cshtml",
-        "Pages/Shared/_LoginPartial.cshtml",
-        "Pages/Shared/_ValidationScriptsPartial.cshtml",
-        "Properties/launchSettings.json",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "MultiOrg"
-    }
-  },
-  "web": {
-    "CSharp": {
-      "Template": "web",
-      "Arguments": "new web",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Properties/launchSettings.json"
-      ],
-      "AuthOption": "None"
-    },
-    "FSharp": {
-      "Template": "web",
-      "Arguments": "new web --language F#",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.fs",
-        "Startup.fs",
-        "Properties/launchSettings.json"
-      ],
-      "AuthOption": "None"
-    }
-  },
-  "webapi": {
-    "IndividualB2C": {
-      "Template": "webapi",
-      "Arguments": "new webapi -au IndividualB2C --aad-b2c-instance https://login.microsoftonline.com/tfp/ --domain fake-b2c-domain.onmicrosoft.com --client-id 64f31f76-2750-49e4-aab9-f5de105b5172 -ssp B2C_1_SiUpIn",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "WeatherForecast.cs",
-        "Controllers/WeatherForecastController.cs",
-        "Properties/launchSettings.json"
-      ],
-      "AuthOption": "IndividualB2C"
-    },
-    "SingleOrg": {
-      "Template": "webapi",
-      "Arguments": "new webapi -au SingleOrg --aad-instance https://login.microsoftonline.com/ --domain fake-aad-domain.onmicrosoft.com --client-id db33c356-12cc-4953-9167-00ad56c2e8b2 --tenant-id 7e511586-66ec-4108-bc9c-a68dee0dc2aa",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "WeatherForecast.cs",
-        "Controllers/WeatherForecastController.cs",
-        "Properties/launchSettings.json"
-      ],
-      "AuthOption": "SingleOrg"
-    },
-    "None": {
-      "Template": "webapi",
-      "Arguments": "new webapi -au None",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "WeatherForecast.cs",
-        "Controllers/WeatherForecastController.cs",
-        "Properties/launchSettings.json"
-      ],
-      "AuthOption": "None"
-    },
-    "Windows": {
-      "Template": "webapi",
-      "Arguments": "new webapi -au Windows",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "WeatherForecast.cs",
-        "Controllers/WeatherForecastController.cs",
-        "Properties/launchSettings.json"
-      ],
-      "AuthOption": "Windows"
-    },
-    "FSharp": {
-      "Template": "webapi",
-      "Arguments": "new webapi --language F#",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.fs",
-        "Startup.fs",
-        "WeatherForecast.fs",
-        "Controllers/WeatherForecastController.fs",
-        "Properties/launchSettings.json"
-      ]
-    }
-  },
-  "mvc": {
-    "Individual": {
-      "Template": "mvc",
-      "Arguments": "new mvc -au Individual",
-      "Files": [
-        "app.db",
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Areas/Identity/Pages/_ViewStart.cshtml",
-        "Controllers/HomeController.cs",
-        "Data/ApplicationDbContext.cs",
-        "Data/Migrations/00000000000000_CreateIdentitySchema.cs",
-        "Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs",
-        "Data/Migrations/ApplicationDbContextModelSnapshot.cs",
-        "Models/ErrorViewModel.cs",
-        "Properties/launchSettings.json",
-        "Views/_ViewImports.cshtml",
-        "Views/_ViewStart.cshtml",
-        "Views/Home/Index.cshtml",
-        "Views/Home/Privacy.cshtml",
-        "Views/Shared/Error.cshtml",
-        "Views/Shared/_Layout.cshtml",
-        "Views/Shared/_LoginPartial.cshtml",
-        "Views/Shared/_ValidationScriptsPartial.cshtml",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "Individual"
-    },
-    "Windows": {
-      "Template": "mvc",
-      "Arguments": "new mvc -au Windows",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Controllers/HomeController.cs",
-        "Models/ErrorViewModel.cs",
-        "Properties/launchSettings.json",
-        "Views/_ViewImports.cshtml",
-        "Views/_ViewStart.cshtml",
-        "Views/Home/Index.cshtml",
-        "Views/Home/Privacy.cshtml",
-        "Views/Shared/Error.cshtml",
-        "Views/Shared/_Layout.cshtml",
-        "Views/Shared/_ValidationScriptsPartial.cshtml",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "Windows"
-    },
-    "SingleOrg": {
-      "Template": "mvc",
-      "Arguments": "new mvc -au SingleOrg --aad-instance https://login.microsoftonline.com/ --domain fake-aad-domain.onmicrosoft.com --client-id db33c356-12cc-4953-9167-00ad56c2e8b2 --tenant-id 7e511586-66ec-4108-bc9c-a68dee0dc2aa",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Controllers/HomeController.cs",
-        "Models/ErrorViewModel.cs",
-        "Properties/launchSettings.json",
-        "Views/_ViewImports.cshtml",
-        "Views/_ViewStart.cshtml",
-        "Views/Home/Index.cshtml",
-        "Views/Home/Privacy.cshtml",
-        "Views/Shared/Error.cshtml",
-        "Views/Shared/_Layout.cshtml",
-        "Views/Shared/_LoginPartial.cshtml",
-        "Views/Shared/_ValidationScriptsPartial.cshtml",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "SingleOrg"
-    },
-    "IndividualB2C": {
-      "Template": "mvc",
-      "Arguments": "new mvc -au IndividualB2C --aad-b2c-instance https://login.microsoftonline.com/tfp/ --domain fake-b2c-domain.onmicrosoft.com --client-id 64f31f76-2750-49e4-aab9-f5de105b5172 -ssp B2C_1_SiUpIn -rp B2C_1_SSPR -ep B2C_1_SiPe",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Controllers/HomeController.cs",
-        "Models/ErrorViewModel.cs",
-        "Properties/launchSettings.json",
-        "Views/_ViewImports.cshtml",
-        "Views/_ViewStart.cshtml",
-        "Views/Home/Index.cshtml",
-        "Views/Home/Privacy.cshtml",
-        "Views/Shared/Error.cshtml",
-        "Views/Shared/_Layout.cshtml",
-        "Views/Shared/_LoginPartial.cshtml",
-        "Views/Shared/_ValidationScriptsPartial.cshtml",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "IndividualB2C"
-    },
-    "None": {
-      "Template": "mvc",
-      "Arguments": "new mvc -au None",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Controllers/HomeController.cs",
-        "Models/ErrorViewModel.cs",
-        "Properties/launchSettings.json",
-        "Views/_ViewImports.cshtml",
-        "Views/_ViewStart.cshtml",
-        "Views/Home/Index.cshtml",
-        "Views/Home/Privacy.cshtml",
-        "Views/Shared/Error.cshtml",
-        "Views/Shared/_Layout.cshtml",
-        "Views/Shared/_ValidationScriptsPartial.cshtml",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "None"
-    },
-    "MultiOrg": {
-      "Template": "mvc",
-      "Arguments": "new mvc -au MultiOrg --aad-instance https://login.microsoftonline.com/ --client-id bac81cbb-9fab-4a4e-9521-be07290c3a51",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "Controllers/HomeController.cs",
-        "Models/ErrorViewModel.cs",
-        "Properties/launchSettings.json",
-        "Views/_ViewImports.cshtml",
-        "Views/_ViewStart.cshtml",
-        "Views/Home/Index.cshtml",
-        "Views/Home/Privacy.cshtml",
-        "Views/Shared/Error.cshtml",
-        "Views/Shared/_Layout.cshtml",
-        "Views/Shared/_LoginPartial.cshtml",
-        "Views/Shared/_ValidationScriptsPartial.cshtml",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ],
-      "AuthOption": "MultiOrg"
-    },
-    "FSharp": {
-      "Template": "mvc",
-      "Arguments": "new mvc --language F#",
-      "Files": [
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.fs",
-        "Startup.fs",
-        "Controllers/HomeController.fs",
-        "Models/ErrorViewModel.fs",
-        "Properties/launchSettings.json",
-        "Views/_ViewImports.cshtml",
-        "Views/_ViewStart.cshtml",
-        "Views/Home/Index.cshtml",
-        "Views/Home/Privacy.cshtml",
-        "Views/Shared/Error.cshtml",
-        "Views/Shared/_Layout.cshtml",
-        "Views/Shared/_ValidationScriptsPartial.cshtml",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/js/site.js",
-        "wwwroot/lib/bootstrap/LICENSE",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
-        "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
-        "wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map",
-        "wwwroot/lib/jquery/LICENSE.txt",
-        "wwwroot/lib/jquery/dist/jquery.js",
-        "wwwroot/lib/jquery/dist/jquery.min.js",
-        "wwwroot/lib/jquery/dist/jquery.min.map",
-        "wwwroot/lib/jquery-validation/LICENSE.md",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.js",
-        "wwwroot/lib/jquery-validation/dist/additional-methods.min.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
-        "wwwroot/lib/jquery-validation/dist/jquery.validate.min.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js",
-        "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
-      ]
-    }
-  },
-  "razorclasslib": {
-    "ComponentsOnly": {
-      "Template": "razorclasslib",
-      "Arguments": "new razorclasslib",
-      "Files": [
-        "wwwroot/background.png",
-        "wwwroot/exampleJsInterop.js",
-        "wwwroot/styles.css",
-        "_Imports.razor",
-        "Component1.razor",
-        "ExampleJsInterop.cs"
-      ]
-    },
-    "ViewsOnly": {
-      "Template": "razorclasslib",
-      "Arguments": "new razorclasslib --support-pages-and-views true",
-      "Files": [
-        "Areas/MyFeature/Pages/Page1.cshtml",
-        "Areas/MyFeature/Pages/Page1.cshtml.cs"
-      ]
-    }
-  },
-  "blazorserver": {
-    "Individual": {
-      "Template": "blazorserver",
-      "Arguments": "new blazorserver -au Individual",
-      "Files": [
-        "app.db",
         "App.razor",
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "_Imports.razor",
-        "Areas/Identity/Pages/Account/LogOut.cshtml",
-        "Areas/Identity/Pages/Shared/_LoginPartial.cshtml",
-        "Areas/Identity/RevalidatingIdentityAuthenticationStateProvider.cs",
-        "Data/ApplicationDbContext.cs",
-        "Data/WeatherForecast.cs",
-        "Data/WeatherForecastService.cs",
-        "Data/Migrations/00000000000000_CreateIdentitySchema.cs",
-        "Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs",
-        "Data/Migrations/ApplicationDbContextModelSnapshot.cs",
         "Pages/Counter.razor",
-        "Pages/Error.razor",
         "Pages/FetchData.razor",
         "Pages/Index.razor",
-        "Pages/_Host.cshtml",
-        "Properties/launchSettings.json",
-        "Shared/LoginDisplay.razor",
-        "Shared/MainLayout.razor",
-        "Shared/NavMenu.razor",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/css/bootstrap/bootstrap.min.css",
-        "wwwroot/css/bootstrap/bootstrap.min.css.map",
-        "wwwroot/css/open-iconic/FONT-LICENSE",
-        "wwwroot/css/open-iconic/ICON-LICENSE",
-        "wwwroot/css/open-iconic/README.md",
-        "wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.eot",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.otf",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.svg",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.ttf",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.woff"
-      ]
-    },
-    "IndividualB2C": {
-      "Template": "blazorserver",
-      "Arguments": "new blazorserver -au IndividualB2C",
-      "Files": [
-        "App.razor",
-        "appsettings.Development.json",
-        "appsettings.json",
         "Program.cs",
-        "Startup.cs",
-        "_Imports.razor",
-        "Data/WeatherForecast.cs",
-        "Data/WeatherForecastService.cs",
-        "Pages/Counter.razor",
-        "Pages/Error.razor",
-        "Pages/FetchData.razor",
-        "Pages/Index.razor",
-        "Pages/_Host.cshtml",
         "Properties/launchSettings.json",
-        "Shared/LoginDisplay.razor",
         "Shared/MainLayout.razor",
         "Shared/NavMenu.razor",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
+        "Shared/SurveyPrompt.razor",
         "wwwroot/css/bootstrap/bootstrap.min.css",
         "wwwroot/css/bootstrap/bootstrap.min.css.map",
-        "wwwroot/css/open-iconic/FONT-LICENSE",
-        "wwwroot/css/open-iconic/ICON-LICENSE",
-        "wwwroot/css/open-iconic/README.md",
         "wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css",
         "wwwroot/css/open-iconic/font/fonts/open-iconic.eot",
         "wwwroot/css/open-iconic/font/fonts/open-iconic.otf",
         "wwwroot/css/open-iconic/font/fonts/open-iconic.svg",
         "wwwroot/css/open-iconic/font/fonts/open-iconic.ttf",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.woff"
-      ]
-    },
-    "MultiOrg": {
-      "Template": "blazorserver",
-      "Arguments": "new blazorserver -au MultiOrg",
-      "Files": [
-        "App.razor",
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "_Imports.razor",
-        "Data/WeatherForecast.cs",
-        "Data/WeatherForecastService.cs",
-        "Pages/Counter.razor",
-        "Pages/Error.razor",
-        "Pages/FetchData.razor",
-        "Pages/Index.razor",
-        "Pages/_Host.cshtml",
-        "Properties/launchSettings.json",
-        "Shared/LoginDisplay.razor",
-        "Shared/MainLayout.razor",
-        "Shared/NavMenu.razor",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/css/bootstrap/bootstrap.min.css",
-        "wwwroot/css/bootstrap/bootstrap.min.css.map",
+        "wwwroot/css/open-iconic/font/fonts/open-iconic.woff",
         "wwwroot/css/open-iconic/FONT-LICENSE",
         "wwwroot/css/open-iconic/ICON-LICENSE",
         "wwwroot/css/open-iconic/README.md",
-        "wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.eot",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.otf",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.svg",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.ttf",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.woff"
+        "wwwroot/css/site.css",
+        "wwwroot/index.html",
+        "wwwroot/sample-data/weather.json",
+        "_Imports.razor"
       ]
     },
-    "None": {
-      "Template": "blazorserver",
-      "Arguments": "new blazorserver",
-      "Files": [
-        "App.razor",
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "_Imports.razor",
-        "Data/WeatherForecast.cs",
-        "Data/WeatherForecastService.cs",
-        "Pages/Error.razor",
-        "Pages/Counter.razor",
-        "Pages/FetchData.razor",
-        "Pages/Index.razor",
-        "Pages/_Host.cshtml",
-        "Properties/launchSettings.json",
-        "Shared/MainLayout.razor",
-        "Shared/NavMenu.razor",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/css/bootstrap/bootstrap.min.css",
-        "wwwroot/css/bootstrap/bootstrap.min.css.map",
-        "wwwroot/css/open-iconic/FONT-LICENSE",
-        "wwwroot/css/open-iconic/ICON-LICENSE",
-        "wwwroot/css/open-iconic/README.md",
-        "wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.eot",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.otf",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.svg",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.ttf",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.woff"
+    "Hosted": {
+      "Template": "blazorwasm",
+      "Arguments": "new blazorwasm -ho",
+      "Files": [
+        "Client/App.razor",
+        "Client/Pages/Counter.razor",
+        "Client/Pages/FetchData.razor",
+        "Client/Pages/Index.razor",
+        "Client/Program.cs",
+        "Client/Properties/launchSettings.json",
+        "Client/Shared/MainLayout.razor",
+        "Client/Shared/NavMenu.razor",
+        "Client/Shared/SurveyPrompt.razor",
+        "Client/wwwroot/css/bootstrap/bootstrap.min.css",
+        "Client/wwwroot/css/bootstrap/bootstrap.min.css.map",
+        "Client/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css",
+        "Client/wwwroot/css/open-iconic/font/fonts/open-iconic.eot",
+        "Client/wwwroot/css/open-iconic/font/fonts/open-iconic.otf",
+        "Client/wwwroot/css/open-iconic/font/fonts/open-iconic.svg",
+        "Client/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf",
+        "Client/wwwroot/css/open-iconic/font/fonts/open-iconic.woff",
+        "Client/wwwroot/css/open-iconic/FONT-LICENSE",
+        "Client/wwwroot/css/open-iconic/ICON-LICENSE",
+        "Client/wwwroot/css/open-iconic/README.md",
+        "Client/wwwroot/css/site.css",
+        "Client/wwwroot/index.html",
+        "Client/_Imports.razor",
+        "Server/Controllers/WeatherForecastController.cs",
+        "Server/Program.cs",
+        "Server/Properties/launchSettings.json",
+        "Server/Startup.cs",
+        "Shared/WeatherForecast.cs"
       ]
     },
-    "SingleOrg": {
-      "Template": "blazorserver",
-      "Arguments": "new blazorserver -au SingleOrg",
+    "PWA": {
+      "Template": "blazorwasm",
+      "Arguments": "new blazorwasm --pwa",
       "Files": [
         "App.razor",
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "_Imports.razor",
-        "Data/WeatherForecast.cs",
-        "Data/WeatherForecastService.cs",
         "Pages/Counter.razor",
-        "Pages/Error.razor",
         "Pages/FetchData.razor",
         "Pages/Index.razor",
-        "Pages/_Host.cshtml",
+        "Program.cs",
         "Properties/launchSettings.json",
-        "Shared/LoginDisplay.razor",
         "Shared/MainLayout.razor",
         "Shared/NavMenu.razor",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
+        "Shared/SurveyPrompt.razor",
         "wwwroot/css/bootstrap/bootstrap.min.css",
         "wwwroot/css/bootstrap/bootstrap.min.css.map",
-        "wwwroot/css/open-iconic/FONT-LICENSE",
-        "wwwroot/css/open-iconic/ICON-LICENSE",
-        "wwwroot/css/open-iconic/README.md",
         "wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css",
         "wwwroot/css/open-iconic/font/fonts/open-iconic.eot",
         "wwwroot/css/open-iconic/font/fonts/open-iconic.otf",
         "wwwroot/css/open-iconic/font/fonts/open-iconic.svg",
         "wwwroot/css/open-iconic/font/fonts/open-iconic.ttf",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.woff"
-      ]
-    },
-    "Windows": {
-      "Template": "blazorserver",
-      "Arguments": "new blazorserver -au Windows",
-      "Files": [
-        "App.razor",
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs",
-        "_Imports.razor",
-        "Data/WeatherForecast.cs",
-        "Data/WeatherForecastService.cs",
-        "Pages/Counter.razor",
-        "Pages/Error.razor",
-        "Pages/FetchData.razor",
-        "Pages/Index.razor",
-        "Pages/_Host.cshtml",
-        "Properties/launchSettings.json",
-        "Shared/LoginDisplay.razor",
-        "Shared/MainLayout.razor",
-        "Shared/NavMenu.razor",
-        "wwwroot/favicon.ico",
-        "wwwroot/css/site.css",
-        "wwwroot/css/bootstrap/bootstrap.min.css",
-        "wwwroot/css/bootstrap/bootstrap.min.css.map",
+        "wwwroot/css/open-iconic/font/fonts/open-iconic.woff",
         "wwwroot/css/open-iconic/FONT-LICENSE",
         "wwwroot/css/open-iconic/ICON-LICENSE",
         "wwwroot/css/open-iconic/README.md",
-        "wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.eot",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.otf",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.svg",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.ttf",
-        "wwwroot/css/open-iconic/font/fonts/open-iconic.woff"
-      ]
-    }
-  },
-  "worker": {
-    "None": {
-      "Template": "worker",
-      "Arguments": "new worker",
-      "Files": [
-        "Properties/launchSettings.json",
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Worker.cs"
-      ]
-    }
-  },
-  "angular": {
-    "None": {
-      "Template": "angular",
-      "Arguments": "new angular",
-      "Files": [
-        "ClientApp/e2e/src/app.e2e-spec.ts",
-        "ClientApp/e2e/src/app.po.ts",
-        "ClientApp/e2e/protractor.conf.js",
-        "ClientApp/e2e/tsconfig.e2e.json",
-        "ClientApp/src/app/counter/counter.component.html",
-        "ClientApp/src/app/counter/counter.component.spec.ts",
-        "ClientApp/src/app/counter/counter.component.ts",
-        "ClientApp/src/app/fetch-data/fetch-data.component.html",
-        "ClientApp/src/app/fetch-data/fetch-data.component.ts",
-        "ClientApp/src/app/home/home.component.html",
-        "ClientApp/src/app/home/home.component.ts",
-        "ClientApp/src/app/nav-menu/nav-menu.component.css",
-        "ClientApp/src/app/nav-menu/nav-menu.component.html",
-        "ClientApp/src/app/nav-menu/nav-menu.component.ts",
-        "ClientApp/src/app/app.component.html",
-        "ClientApp/src/app/app.component.ts",
-        "ClientApp/src/app/app.module.ts",
-        "ClientApp/src/app/app.server.module.ts",
-        "ClientApp/src/assets/.gitkeep",
-        "ClientApp/src/environments/environment.prod.ts",
-        "ClientApp/src/environments/environment.ts",
-        "ClientApp/browserslist",
-        "ClientApp/src/index.html",
-        "ClientApp/src/karma.conf.js",
-        "ClientApp/src/main.ts",
-        "ClientApp/src/polyfills.ts",
-        "ClientApp/src/styles.css",
-        "ClientApp/src/test.ts",
-        "ClientApp/src/tsconfig.app.json",
-        "ClientApp/src/tsconfig.server.json",
-        "ClientApp/src/tsconfig.spec.json",
-        "ClientApp/src/tslint.json",
-        "ClientApp/.editorconfig",
-        "ClientApp/.gitignore",
-        "ClientApp/angular.json",
-        "ClientApp/package-lock.json",
-        "ClientApp/package.json",
-        "ClientApp/README.md",
-        "ClientApp/tsconfig.json",
-        "ClientApp/tslint.json",
-        "WeatherForecast.cs",
-        "Controllers/WeatherForecastController.cs",
-        "Pages/_ViewImports.cshtml",
-        "Pages/Error.cshtml",
-        "Pages/Error.cshtml.cs",
-        "Properties/launchSettings.json",
-        "wwwroot/favicon.ico",
-        ".gitignore",
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs"
-      ]
-    }
-  },
-  "react": {
-    "None": {
-      "Template": "react",
-      "Arguments": "new react",
-      "Files": [
-        "ClientApp/public/favicon.ico",
-        "ClientApp/public/index.html",
-        "ClientApp/public/manifest.json",
-        "ClientApp/src/components/Counter.js",
-        "ClientApp/src/components/FetchData.js",
-        "ClientApp/src/components/Home.js",
-        "ClientApp/src/components/Layout.js",
-        "ClientApp/src/components/NavMenu.css",
-        "ClientApp/src/components/NavMenu.js",
-        "ClientApp/src/custom.css",
-        "ClientApp/src/App.js",
-        "ClientApp/src/App.test.js",
-        "ClientApp/src/index.js",
-        "ClientApp/src/registerServiceWorker.js",
-        "ClientApp/.gitignore",
-        "ClientApp/package-lock.json",
-        "ClientApp/package.json",
-        "ClientApp/README.md",
-        "WeatherForecast.cs",
-        "Controllers/WeatherForecastController.cs",
-        "Pages/_ViewImports.cshtml",
-        "Pages/Error.cshtml",
-        "Pages/Error.cshtml.cs",
-        "Properties/launchSettings.json",
-        ".gitignore",
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs"
-      ]
-    }
-  },
-  "reactredux": {
-    "None": {
-      "Template": "reactredux",
-      "Arguments": "new reactredux",
-      "Files": [
-        "ClientApp/public/favicon.ico",
-        "ClientApp/public/index.html",
-        "ClientApp/public/manifest.json",
-        "ClientApp/src/components/Counter.tsx",
-        "ClientApp/src/components/FetchData.tsx",
-        "ClientApp/src/components/Home.tsx",
-        "ClientApp/src/components/Layout.tsx",
-        "ClientApp/src/components/NavMenu.css",
-        "ClientApp/src/components/NavMenu.tsx",
-        "ClientApp/src/store/configureStore.ts",
-        "ClientApp/src/store/Counter.ts",
-        "ClientApp/src/store/index.ts",
-        "ClientApp/src/store/WeatherForecasts.ts",
-        "ClientApp/src/App.tsx",
-        "ClientApp/src/App.test.tsx",
-        "ClientApp/src/custom.css",
-        "ClientApp/src/index.tsx",
-        "ClientApp/src/react-app-env.d.ts",
-        "ClientApp/src/registerServiceWorker.ts",
-        "ClientApp/.eslintrc.json",
-        "ClientApp/.gitignore",
-        "ClientApp/package-lock.json",
-        "ClientApp/package.json",
-        "ClientApp/README.md",
-        "ClientApp/tsconfig.json",
-        "WeatherForecast.cs",
-        "Controllers/WeatherForecastController.cs",
-        "Pages/_ViewImports.cshtml",
-        "Pages/Error.cshtml",
-        "Pages/Error.cshtml.cs",
-        "Properties/launchSettings.json",
-        ".gitignore",
-        "appsettings.Development.json",
-        "appsettings.json",
-        "Program.cs",
-        "Startup.cs"
+        "wwwroot/css/site.css",
+        "wwwroot/icon-512.png",
+        "wwwroot/index.html",
+        "wwwroot/manifest.json",
+        "wwwroot/sample-data/weather.json",
+        "wwwroot/service-worker.js",
+        "wwwroot/service-worker.published.js",
+        "_Imports.razor"
       ]
     }
   }
diff --git a/src/Shared/E2ETesting/BrowserFixture.cs b/src/Shared/E2ETesting/BrowserFixture.cs
index bf8b31d2942..a00c1410978 100644
--- a/src/Shared/E2ETesting/BrowserFixture.cs
+++ b/src/Shared/E2ETesting/BrowserFixture.cs
@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Concurrent;
 using System.Diagnostics;
+using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Runtime.InteropServices;
@@ -68,6 +69,15 @@ namespace Microsoft.AspNetCore.E2ETesting
             {
                 browser.Dispose();
             }
+
+            foreach (var context in _browsers.Keys)
+            {
+                var userProfileDirectory = UserProfileDirectory(context);
+                if (!string.IsNullOrEmpty(userProfileDirectory) && Directory.Exists(userProfileDirectory))
+                {
+                    Directory.Delete(userProfileDirectory, recursive: true);
+                }
+            }
         }
 
         public Task<(IWebDriver, ILogs)> GetOrCreateBrowserAsync(ITestOutputHelper output, string isolationContext = "")
@@ -116,6 +126,13 @@ namespace Microsoft.AspNetCore.E2ETesting
                 output.WriteLine($"Set {nameof(ChromeOptions)}.{nameof(opts.BinaryLocation)} to {binaryLocation}");
             }
 
+            var userProfileDirectory = UserProfileDirectory(context);
+            if (!string.IsNullOrEmpty(userProfileDirectory))
+            {
+                Directory.CreateDirectory(userProfileDirectory);
+                opts.AddArgument($"--user-data-dir={userProfileDirectory}");
+            }
+
             var instance = await SeleniumStandaloneServer.GetInstanceAsync(output);
 
             var attempt = 0;
@@ -155,6 +172,16 @@ namespace Microsoft.AspNetCore.E2ETesting
             throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is irresponsive");
         }
 
+        private string UserProfileDirectory(string context)
+        {
+            if (string.IsNullOrEmpty(context))
+            {
+                return null;
+            }
+
+            return Path.Combine(Path.GetTempPath(), "BrowserFixtureUserProfiles", context);
+        }
+
         private async Task<(IWebDriver browser, ILogs log)> CreateSauceBrowserAsync(string context, ITestOutputHelper output)
         {
             var sauce = E2ETestOptions.Instance.Sauce;
-- 
GitLab