diff --git a/src/Microsoft.Tye.Hosting/Watch/assets/DotNetWatch.targets b/src/Microsoft.Tye.Hosting/Watch/assets/DotNetWatch.targets
index 09422225..087d4ed4 100644
--- a/src/Microsoft.Tye.Hosting/Watch/assets/DotNetWatch.targets
+++ b/src/Microsoft.Tye.Hosting/Watch/assets/DotNetWatch.targets
@@ -26,6 +26,7 @@ Returns: @(Watch)
<_CollectWatchItemsDependsOn Condition=" '$(TargetFramework)' != '' ">
_CoreCollectWatchItems;
+ $(CustomCollectWatchItems);
diff --git a/test/E2ETest/TyeRunTests.cs b/test/E2ETest/TyeRunTests.cs
index c8a4dd2f..a9e625ed 100644
--- a/test/E2ETest/TyeRunTests.cs
+++ b/test/E2ETest/TyeRunTests.cs
@@ -381,6 +381,54 @@ services:
});
}
+ [Fact]
+ public async Task WebAppWatchRunTest()
+ {
+ using var projectDirectory = CopyTestProjectDirectory("web-app");
+
+ var projectFile = new FileInfo(Path.Combine(projectDirectory.DirectoryPath, "tye.yaml"));
+ var outputContext = new OutputContext(_sink, Verbosity.Debug);
+ var application = await ApplicationFactory.CreateAsync(outputContext, projectFile);
+
+ var handler = new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = (a, b, c, d) => true,
+ AllowAutoRedirect = false
+ };
+
+ var client = new HttpClient(new RetryHandler(handler));
+
+ await RunHostingApplication(application, new HostOptions() { Watch = true }, async (app, uri) =>
+ {
+ // make sure app is running
+ var appUri = await GetServiceUrl(client, uri, "web-app");
+
+ var response = await client.GetAsync(appUri);
+
+ Assert.True(response.IsSuccessStatusCode);
+
+ var startupPath = Path.Combine(projectDirectory.DirectoryPath, "Pages", "index.cshtml");
+ File.AppendAllText(startupPath, "\n");
+
+ const int retries = 10;
+ for (var i = 0; i < retries; i++)
+ {
+
+ var logs = await client.GetStringAsync(new Uri(uri, $"/api/v1/logs/web-app"));
+
+ // "Application Started" should be logged twice due to the file change
+ if (logs.IndexOf("Application started") != logs.LastIndexOf("Application started"))
+ {
+ return;
+ }
+
+ await Task.Delay(5000);
+ }
+
+ throw new Exception("Failed to relaunch project with dotnet watch");
+ });
+ }
+
[Fact]
public async Task DockerBaseImageAndTagTest()
{
diff --git a/test/E2ETest/testassets/projects/web-app/Pages/Error.cshtml b/test/E2ETest/testassets/projects/web-app/Pages/Error.cshtml
new file mode 100644
index 00000000..3961204c
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/Pages/Error.cshtml
@@ -0,0 +1,26 @@
+@page
+@model ErrorModel
+@{
+ ViewData["Title"] = "Error";
+}
+
+
Error.
+An error occurred while processing your request.
+
+@if (Model.ShowRequestId)
+{
+
+ Request ID: @Model.RequestId
+
+}
+
+Development Mode
+
+ Swapping to the Development environment displays detailed information about the error that occurred.
+
+
+ The Development environment shouldn't be enabled for deployed applications.
+ It can result in displaying sensitive information from exceptions to end users.
+ For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
+ and restarting the app.
+
diff --git a/test/E2ETest/testassets/projects/web-app/Pages/Error.cshtml.cs b/test/E2ETest/testassets/projects/web-app/Pages/Error.cshtml.cs
new file mode 100644
index 00000000..2abfdbf3
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/Pages/Error.cshtml.cs
@@ -0,0 +1,27 @@
+using System.Diagnostics;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace web_app.Pages
+{
+ [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
+ public class ErrorModel : PageModel
+ {
+ public string RequestId { get; set; }
+
+ public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
+
+ private readonly ILogger _logger;
+
+ public ErrorModel(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public void OnGet()
+ {
+ RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
+ }
+ }
+}
diff --git a/test/E2ETest/testassets/projects/web-app/Pages/Index.cshtml b/test/E2ETest/testassets/projects/web-app/Pages/Index.cshtml
new file mode 100644
index 00000000..6fb28a20
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/Pages/Index.cshtml
@@ -0,0 +1,10 @@
+@page
+@model IndexModel
+@{
+ ViewData["Title"] = "Home page";
+}
+
+
diff --git a/test/E2ETest/testassets/projects/web-app/Pages/Index.cshtml.cs b/test/E2ETest/testassets/projects/web-app/Pages/Index.cshtml.cs
new file mode 100644
index 00000000..511f208d
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/Pages/Index.cshtml.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace web_app.Pages
+{
+ public class IndexModel : PageModel
+ {
+ private readonly ILogger _logger;
+
+ public IndexModel(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public void OnGet()
+ {
+
+ }
+ }
+}
diff --git a/test/E2ETest/testassets/projects/web-app/Pages/Shared/_Layout.cshtml b/test/E2ETest/testassets/projects/web-app/Pages/Shared/_Layout.cshtml
new file mode 100644
index 00000000..926d3f6e
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/Pages/Shared/_Layout.cshtml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ @ViewData["Title"] - web_app
+
+
+
+
+
+
+ @RenderBody()
+
+
+
+
+
+ @RenderSection("Scripts", required: false)
+
+
diff --git a/test/E2ETest/testassets/projects/web-app/Pages/_ViewImports.cshtml b/test/E2ETest/testassets/projects/web-app/Pages/_ViewImports.cshtml
new file mode 100644
index 00000000..960236ee
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/Pages/_ViewImports.cshtml
@@ -0,0 +1,3 @@
+@using web_app
+@namespace web_app.Pages
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/test/E2ETest/testassets/projects/web-app/Pages/_ViewStart.cshtml b/test/E2ETest/testassets/projects/web-app/Pages/_ViewStart.cshtml
new file mode 100644
index 00000000..820a2f6e
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/Pages/_ViewStart.cshtml
@@ -0,0 +1,3 @@
+@{
+ Layout = "_Layout";
+}
diff --git a/test/E2ETest/testassets/projects/web-app/Program.cs b/test/E2ETest/testassets/projects/web-app/Program.cs
new file mode 100644
index 00000000..5f709b73
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/Program.cs
@@ -0,0 +1,20 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+
+namespace web_app
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ CreateHostBuilder(args).Build().Run();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup();
+ });
+ }
+}
diff --git a/test/E2ETest/testassets/projects/web-app/Properties/launchSettings.json b/test/E2ETest/testassets/projects/web-app/Properties/launchSettings.json
new file mode 100644
index 00000000..4ae9703b
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:19132",
+ "sslPort": 44328
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "web_app": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/test/E2ETest/testassets/projects/web-app/Startup.cs b/test/E2ETest/testassets/projects/web-app/Startup.cs
new file mode 100644
index 00000000..7b9e3945
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/Startup.cs
@@ -0,0 +1,50 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace web_app
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddRazorPages();
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+ else
+ {
+ app.UseExceptionHandler("/Error");
+ // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
+ app.UseHsts();
+ }
+
+ app.UseStaticFiles();
+
+ app.UseRouting();
+
+ app.UseAuthorization();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapRazorPages();
+ });
+ }
+ }
+}
diff --git a/test/E2ETest/testassets/projects/web-app/appsettings.Development.json b/test/E2ETest/testassets/projects/web-app/appsettings.Development.json
new file mode 100644
index 00000000..8983e0fc
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/appsettings.Development.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ }
+}
diff --git a/test/E2ETest/testassets/projects/web-app/appsettings.json b/test/E2ETest/testassets/projects/web-app/appsettings.json
new file mode 100644
index 00000000..d9d9a9bf
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/test/E2ETest/testassets/projects/web-app/tye.yaml b/test/E2ETest/testassets/projects/web-app/tye.yaml
new file mode 100644
index 00000000..08d8796e
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/tye.yaml
@@ -0,0 +1,10 @@
+# tye application configuration file
+# read all about it at https://github.com/dotnet/tye
+#
+# when you've given us a try, we'd love to know what you think:
+# https://aka.ms/AA7q20u
+#
+name: web-app
+services:
+- name: web-app
+ project: web-app.csproj
\ No newline at end of file
diff --git a/test/E2ETest/testassets/projects/web-app/web-app.csproj b/test/E2ETest/testassets/projects/web-app/web-app.csproj
new file mode 100644
index 00000000..d6bda1c7
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/web-app.csproj
@@ -0,0 +1,8 @@
+
+
+
+ netcoreapp3.1
+ web_app
+
+
+
diff --git a/test/E2ETest/testassets/projects/web-app/wwwroot/css/site.css b/test/E2ETest/testassets/projects/web-app/wwwroot/css/site.css
new file mode 100644
index 00000000..e679a8ea
--- /dev/null
+++ b/test/E2ETest/testassets/projects/web-app/wwwroot/css/site.css
@@ -0,0 +1,71 @@
+/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
+for details on configuring this project to bundle and minify static web assets. */
+
+a.navbar-brand {
+ white-space: normal;
+ text-align: center;
+ word-break: break-all;
+}
+
+/* Provide sufficient contrast against white background */
+a {
+ color: #0366d6;
+}
+
+.btn-primary {
+ color: #fff;
+ background-color: #1b6ec2;
+ border-color: #1861ac;
+}
+
+.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
+ color: #fff;
+ background-color: #1b6ec2;
+ border-color: #1861ac;
+}
+
+/* Sticky footer styles
+-------------------------------------------------- */
+html {
+ font-size: 14px;
+}
+@media (min-width: 768px) {
+ html {
+ font-size: 16px;
+ }
+}
+
+.border-top {
+ border-top: 1px solid #e5e5e5;
+}
+.border-bottom {
+ border-bottom: 1px solid #e5e5e5;
+}
+
+.box-shadow {
+ box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
+}
+
+button.accept-policy {
+ font-size: 1rem;
+ line-height: inherit;
+}
+
+/* Sticky footer styles
+-------------------------------------------------- */
+html {
+ position: relative;
+ min-height: 100%;
+}
+
+body {
+ /* Margin bottom by footer height */
+ margin-bottom: 60px;
+}
+.footer {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ white-space: nowrap;
+ line-height: 60px; /* Vertically center the text there */
+}
diff --git a/test/E2ETest/testassets/projects/web-app/wwwroot/favicon.ico b/test/E2ETest/testassets/projects/web-app/wwwroot/favicon.ico
new file mode 100644
index 00000000..a3a79998
Binary files /dev/null and b/test/E2ETest/testassets/projects/web-app/wwwroot/favicon.ico differ