dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.46k stars 10.03k forks source link

Add a feature to dotnet-watch to launch a browser and then auto-refresh it when changes are detected #23412

Closed pranavkm closed 4 years ago

pranavkm commented 4 years ago

As a start, we'd like for a way to launch a browser, once a server the server is listening. Once we have this, we can start looking at ways to refresh the current tab rather than launching a new instance. @DamianEdwards suggested looking at https://github.com/RickStrahl/Westwind.AspnetCore.LiveReload (cc @RickStrahl)'s middleware to see how that's achieved and if that's something we could incorporate in to dotnet-watch.

DamianEdwards commented 4 years ago

Rough overview of how this will work. We can update this as necessary.

We'll add a new IHostingStartup implementation to the ASP.NET Core shared framework in a new assembly (e.g. Microsoft.AspNetCore.AutoBrowserReload) that ships in the dotnet watch location, that when invoked registers an auto-reload middleware in the app early in the pipeline (middleware also in the shared framework).

The middleware detects if the request response type is HTML and if so, injects a block of JavaScript that connects to a WebSocket that will communicate when the browser page should be reloaded.

In its default, non-configured mode, the middleware will also add a WebSocket endpoint to the app (e.g. /__auto_reload) that the injected JavaScript block will connect to, and it starts monitoring the configured web root of the app along with any web roots from dependent projects (e.g. references to Razor class library projects) for changes to relevant static files (e.g. .js, .css, .jpg, .png, .ico) (NOTE: Supporting configuration of which static file types to watch might be desirable and if done should resemble the configuration of watched files for dotnet watch via MSBuild item attributes)

Any change to these files will be treated as cause to auto-reload the browser window. If the user wishes to enable this, they only need set the required environment variable to enable discovery of the IHostingStartup and then launching the app via dotnet run will result in the auto-reload middleware being injected in this mode, where the app watches static files for changes and auto-reloads the browser window when they change (similar to how @RickStrahl's live reload middleware works today but without requiring application code changes).

When dotnet watch launches the app, it will set the required environment variables to:

  1. Inject a startup hook that loads the assembly
  2. Enable the IHostingStartup
  3. Configure the WebSocket endpoint that the JS block connects back to to be one that dotnet watch itself is hosting, via a configuration value that the IHostingStartup looks for (e.g. ASPNETCORE_AUTO_RELOAD_WS_ENDPOINT).

If this configuration value is set, the auto-reload middleware will not add the in-app WebSocket endpoint or start monitoring for static file changes, but rather configure the JS block to connect to the endpoint address configured and hosted by dotnet watch, e.g. https://localhost:12345/__auto_reload *

dotnet watch will launch its own server that hosts a WebSocket endpoint that the JS block emitted by the middleware injected into the app will connect to. (NOTE: dotnet watch will need to boot this endpoint using the ASP.NET Core HTTPS dev cert so that cross-domain connections are always allowed, might need to be configured for CORS too, not sure).

As dotnet watch will be coordinating the file watching in this mode, it will need to start watching for relevant static file changes too (which I don't believe it does today). As stated above, The existing MSBuild-based method for configuring the watch file list should be expanded to support setting which static files to watch also.

To trigger the JS block to initiate the browser window refresh, the server (whether in-app or in dotnet watch or possibly VS in the future) should simply terminate the WebSocket connection. The JS block should treat any disconnection of the WebSocket as indication it needs to perform the browser refresh. In the case of dotnet watch, This should only occur after it has been determined that the application server has successfully restarted after the project has been rebuilt and restarted (e.g. by monitoring the console output for the message indicating the web server is now listening, or perhaps a more robust solution would be to have the injected IHostingStartup signal back to dotnet watch once the app server has finished starting, via another endpoint hosted by dotnet watch for such coordination).

Additionally, on first launch, dotnet watch will read the content of the project's launchSettings.json file to see if the "launchBrowser" flag is enabled in the profile named after the project (this flag is used today by VS). If so, it will launch the browser after the server has started (see detection discussion above) at the URL configured in the "launchUrl" property of the same launch profile if set, otherwise at the application URL as configured in the "applicationUrl" property (NOTE: this property is semi-colon delimited so just the first URL configured should be used).

davidfowl commented 4 years ago

We'll add a new IHostingStartup implementation to the ASP.NET Core shared framework in a new assembly (e.g. Microsoft.AspNetCore.AutoBrowserReload) that when invoked registers an auto-reload middleware in the app early in the pipeline (middleware also in the shared framework).

Put it in another assembly outside of the shared framework. It can be in an assembly that ships with dotnet watch and is injected via a startup hook (injected with an environment variable). That same assembly would have the IHostingStartup in it and dotnet watch would inject it when it launches the process.

DamianEdwards commented 4 years ago

OK, fair. Let's scope it down to the dotnet watch based scenarios for now and thus avoid any of the in-app WebSocket or file-watching additions. This will be a feature of dotnet watch and we can revisit other configurations as necessary.