From 7d7f7763bae7e2f29e9e4a53017dcb8597c66654 Mon Sep 17 00:00:00 2001 From: John Campion Date: Tue, 5 Jan 2021 17:26:13 -0500 Subject: [PATCH 1/4] Found that the default mappings work best Also added forcekill to force existing dev-servers shutdown --- .vscode/launch.json | 17 +++-------------- Startup.cs | 2 +- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 007497c..418c329 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,27 +37,16 @@ "type": "chrome", "request": "launch", "url": "http://localhost:5000", - "webRoot": "${workspaceFolder}/ClientApp", + "webRoot": "${workspaceFolder}/ClientApp/", "breakOnLoad": true, - "sourceMaps": true, - "sourceMapPathOverrides": { - "webpack:///*": "${webRoot}/*", - "webpack:///./*": "${webRoot}/*", - "webpack:///src/*": "${webRoot}/*" - } + "sourceMaps": true }, { "name": "Launch Firefox", "type": "firefox", "request": "launch", "url": "http://localhost:5000", - "webRoot": "${workspaceFolder}/ClientApp", - "pathMappings": [ - { - "url": "webpack:///src/", - "path": "${webRoot}/" - } - ] + "webRoot": "${workspaceFolder}/ClientApp/" } ], "compounds": [ diff --git a/Startup.cs b/Startup.cs index 689ec8e..7fd9d93 100644 --- a/Startup.cs +++ b/Startup.cs @@ -74,7 +74,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) { // run npm process with client app - spa.UseVueCli(npmScript: "serve", port: 8080); + spa.UseVueCli(npmScript: "serve", port: 8080, forceKill: true); // if you just prefer to proxy requests from client app, use proxy to SPA dev server instead, // app should be already running before starting a .NET client: // spa.UseProxyToSpaDevelopmentServer("http://localhost:8080"); // your Vue app port From 61fd2d5799f503b23a0f9bec9742f76fb7984afe Mon Sep 17 00:00:00 2001 From: John Campion Date: Tue, 5 Jan 2021 21:14:26 -0500 Subject: [PATCH 2/4] Added command line parameters These will allow for some new debugging options in VS Code --- CommandLine.cs | 145 +++++++++++++++++++++++++++++++++++++++++++++++++ Program.cs | 14 +++-- Startup.cs | 17 +++++- 3 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 CommandLine.cs diff --git a/CommandLine.cs b/CommandLine.cs new file mode 100644 index 0000000..4cb85dd --- /dev/null +++ b/CommandLine.cs @@ -0,0 +1,145 @@ +// original source: https://www.codeproject.com/Articles/3111/C-NET-Command-Line-Arguments-Parser + +using System; +using System.Collections.Specialized; +using System.Text.RegularExpressions; + +namespace CommandLine +{ + public static class Arguments + { + + public static bool TryGetOptions(string[] args, bool inConsole, out string mode, out ushort port, out bool https) + { + var arguments = Parse(args); + var validArgs = true; + + mode = arguments["m"] ?? arguments["mode"] ?? "start"; + https = arguments["http"] == null && arguments["https"] != null; + var portString = arguments["p"] ?? arguments["port"] ?? "8080"; + + if (mode != "start" && mode != "attach" && mode != "kill") + { + if (inConsole) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Invalid mode; Allowed values are start | attach | kill"); + Console.ResetColor(); + } + validArgs = false; + } + + if (!ushort.TryParse(portString, out port) || port < 80) + { + if (inConsole) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Invalid port number specified."); + Console.ResetColor(); + } + validArgs = false; + } + + if (arguments["h"] != null || arguments["help"] != null) validArgs = false; + + if (inConsole && !validArgs) + { + Console.WriteLine(); + Console.WriteLine(" Mode Argument Options (Defaults to start)"); + Console.WriteLine(" -m | --mode start -> Start the SPA server and proxy to that."); + Console.WriteLine(" -m | --mode attach -> Attach to existing SPA server"); + Console.WriteLine(" -m | --mode kill -> Shutdown any existing SPA server on the specified port (used after debugging in VS Code)"); + Console.WriteLine(); + Console.WriteLine(" Port Argument (Defaults to 8080)"); + Console.WriteLine(" -p | --port 8080 -> Specify what port to start or attach to, minimum of 80"); + Console.WriteLine(); + Console.WriteLine(" HTTPS (Defaults to false)"); + Console.WriteLine(" -https -> Uses HTTPS"); + Console.WriteLine(); + + } + + return validArgs; + + } + + public static StringDictionary Parse(string[] args) + { + var parameters = new StringDictionary(); + Regex splitter = new Regex(@"^-{1,2}|^/|=|:", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + + Regex remover = new Regex(@"^['""]?(.*?)['""]?$", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + + string parameter = null; + string[] parts; + + // Valid parameters forms: + // {-,/,--}param{ ,=,:}((",')value(",')) + // Examples: + // -param1 value1 --param2 /param3:"Test-:-work" + // /param4=happy -param5 '--=nice=--' + foreach (string txt in args) + { + // Look for new parameters (-,/ or --) and a + // possible enclosed value (=,:) + parts = splitter.Split(txt, 3); + + switch (parts.Length) + { + // Found a value (for the last parameter + // found (space separator)) + case 1: + if (parameter != null) + { + if (!parameters.ContainsKey(parameter)) + { + parts[0] = + remover.Replace(parts[0], "$1"); + + parameters.Add(parameter, parts[0]); + } + parameter = null; + } + // else Error: no parameter waiting for a value (skipped) + break; + + // Found just a parameter + case 2: + // The last parameter is still waiting. + // With no value, set it to true. + if (parameter != null && !parameters.ContainsKey(parameter)) + parameters.Add(parameter, "true"); + + parameter = parts[1]; + break; + + // Parameter with enclosed value + case 3: + // The last parameter is still waiting. + // With no value, set it to true. + if (parameter != null && !parameters.ContainsKey(parameter)) + parameters.Add(parameter, "true"); + + parameter = parts[1]; + + // Remove possible enclosing characters (",') + if (!parameters.ContainsKey(parameter)) + { + parts[2] = remover.Replace(parts[2], "$1"); + parameters.Add(parameter, parts[2]); + } + + parameter = null; + break; + } + } + // In case a parameter is still waiting + if (parameter != null && !parameters.ContainsKey(parameter)) + parameters.Add(parameter, "true"); + + return parameters; + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index e32eb14..02c949f 100644 --- a/Program.cs +++ b/Program.cs @@ -1,11 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using VueCliMiddleware; namespace AspNetCoreVueStarter { @@ -13,6 +9,14 @@ public class Program { public static void Main(string[] args) { + if (!CommandLine.Arguments.TryGetOptions(args, true, out string mode, out ushort port, out bool https)) return; + + if (mode == "kill") { + Console.WriteLine($"Killing process serving port {port}..."); + PidUtils.KillPort(port, true, true); + return; + } + CreateHostBuilder(args).Build().Run(); } diff --git a/Startup.cs b/Startup.cs index 7fd9d93..e804d11 100644 --- a/Startup.cs +++ b/Startup.cs @@ -34,6 +34,9 @@ public void ConfigureServices(IServiceCollection services) // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + + _ = CommandLine.Arguments.TryGetOptions(System.Environment.GetCommandLineArgs(), false, out string mode, out ushort port, out bool https); + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); @@ -45,7 +48,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseHsts(); } - // app.UseHttpsRedirection(); + if (https) app.UseHttpsRedirection(); + app.UseStaticFiles(); if (!env.IsDevelopment()) { @@ -73,11 +77,18 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) { + // run npm process with client app - spa.UseVueCli(npmScript: "serve", port: 8080, forceKill: true); + if (mode == "start") { + spa.UseVueCli(npmScript: "serve", port: port, forceKill: true, https: https); + } + // if you just prefer to proxy requests from client app, use proxy to SPA dev server instead, // app should be already running before starting a .NET client: - // spa.UseProxyToSpaDevelopmentServer("http://localhost:8080"); // your Vue app port + // run npm process with client app + if (mode == "attach") { + spa.UseProxyToSpaDevelopmentServer($"{(https ? "https" : "http")}://localhost:{port}"); // your Vue app port + } } }); } From aa0beb8173d379b56f235d34e0fb04bcf66fd3b8 Mon Sep 17 00:00:00 2001 From: John Campion Date: Tue, 5 Jan 2021 21:24:47 -0500 Subject: [PATCH 3/4] Enabled killing of vue-cli process after debugging stopped --- .vscode/launch.json | 2 ++ .vscode/tasks.json | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 418c329..6afacfb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,6 +13,8 @@ "program": "${workspaceFolder}/bin/Debug/net5.0/AspNetCoreVueStarter.dll", "args": [], "cwd": "${workspaceFolder}", + // this will kill any stray vue-cli processes, as the .NET app can't shut them down when it is killed by the debugger + "postDebugTask": "kill", "stopAtEntry": false, // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser "serverReadyAction": { diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 93332af..5fcdd98 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -37,6 +37,17 @@ "/consoleloggerparameters:NoSummary" ], "problemMatcher": "$msCompile" + }, + { + "label": "kill", + "command": "dotnet", + "type": "process", + "args": [ + "run", + "${workspaceFolder}/bin/Debug/net5.0/AspNetCoreVueStarter.dll", + "/mode:kill" + ], + "problemMatcher": [] } ] } \ No newline at end of file From 49bd29641892a1ac142415c285bc073a5e7fb75d Mon Sep 17 00:00:00 2001 From: John Campion Date: Tue, 5 Jan 2021 23:25:35 -0500 Subject: [PATCH 4/4] Debugging Config Allows both VS Code launched Vue-Cli server and internal .NET Core Launch. --- .vscode/launch.json | 81 ++++++++++++++++++++++++++--------------- .vscode/tasks.json | 55 ++++++++++++++++++++++++++++ ClientApp/vue.config.js | 2 +- 3 files changed, 107 insertions(+), 31 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 6afacfb..c941de7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,28 +5,65 @@ "version": "0.2.0", "configurations": [ { - "name": ".NET Core Launch (web)", + "name": ".NET Core & Vue Server", "type": "coreclr", "request": "launch", - "preLaunchTask": "build", + "logging": { + "moduleLoad": false + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development", + "VUE_DEV_SERVER_PROGRESS": "true" + }, + "cwd": "${workspaceFolder}", + "preLaunchTask": "build-vue-cli-serve", // If you have changed target frameworks, make sure to update the program path. "program": "${workspaceFolder}/bin/Debug/net5.0/AspNetCoreVueStarter.dll", - "args": [], - "cwd": "${workspaceFolder}", - // this will kill any stray vue-cli processes, as the .NET app can't shut them down when it is killed by the debugger - "postDebugTask": "kill", + "args": [ + "/mode:attach" + ], "stopAtEntry": false, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + }, // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser "serverReadyAction": { - "action": "openExternally", - "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + "action": "startDebugging", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)", + "name": "Chrome Browser" + }, + // this will kill any stray vue-cli processes, as the .NET app can't shut them down when it is killed by the debugger + "postDebugTask": "kill" + }, + { + "name": ".NET Core Web", + "type": "coreclr", + "request": "launch", + "logging": { + "moduleLoad": false }, "env": { "ASPNETCORE_ENVIRONMENT": "Development" }, + "cwd": "${workspaceFolder}", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/bin/Debug/net5.0/AspNetCoreVueStarter.dll", + "args": [ + "/mode:start" + ], + "stopAtEntry": false, "sourceFileMap": { "/Views": "${workspaceFolder}/Views" - } + }, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "startDebugging", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)", + "name": "Chrome Browser" + }, + // this will kill any stray vue-cli processes, as the .NET app can't shut them down when it is killed by the debugger + "postDebugTask": "kill" }, { "name": ".NET Core Attach", @@ -35,36 +72,20 @@ "processId": "${command:pickProcess}" }, { - "name": "Launch Chrome", + "name": "Chrome Browser", "type": "chrome", "request": "launch", "url": "http://localhost:5000", - "webRoot": "${workspaceFolder}/ClientApp/", + "webRoot": "${workspaceFolder}/ClientApp", "breakOnLoad": true, - "sourceMaps": true + "sourceMaps": true, }, { - "name": "Launch Firefox", + "name": "Firefox Browser", "type": "firefox", "request": "launch", "url": "http://localhost:5000", - "webRoot": "${workspaceFolder}/ClientApp/" - } - ], - "compounds": [ - { - "name": "Debug SPA and API (Chrome)", - "configurations": [ - ".NET Core Launch (web)", - "Launch Chrome" - ] - }, - { - "name": "Debug SPA and API (Firefox)", - "configurations": [ - ".NET Core Launch (web)", - "Launch Firefox" - ] + "webRoot": "${workspaceFolder}/ClientApp" } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 5fcdd98..056ed3b 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -47,7 +47,62 @@ "${workspaceFolder}/bin/Debug/net5.0/AspNetCoreVueStarter.dll", "/mode:kill" ], + "presentation": { + "echo": false, + "reveal": "never", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false + }, "problemMatcher": [] + }, + { + "label": "vue-cli-serve", + "command": "npm", + "type": "shell", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}/ClientApp/", + "env": { + "VUE_DEV_SERVER_PROGRESS": "true" + } + }, + "args": [ + "run", + "serve" + ], + "presentation": { + "echo": false, + "reveal": "always", + "focus": false, + "panel": "dedicated", + "showReuseMessage": false, + "clear": true + }, + "problemMatcher": [ + { + "pattern": [ + { + "regexp": ".", + "file": 1, + "location": 2, + "message": 3 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": ".", + "endsPattern": "App running at", + } + } + ] + }, + { + "label": "build-vue-cli-serve", + "dependsOn": ["build", "vue-cli-serve"], + "dependsOrder": "sequence", } + ] } \ No newline at end of file diff --git a/ClientApp/vue.config.js b/ClientApp/vue.config.js index 7e2a93e..116e012 100644 --- a/ClientApp/vue.config.js +++ b/ClientApp/vue.config.js @@ -4,6 +4,6 @@ module.exports = { }, transpileDependencies: ['vuetify'], devServer: { - progress: false + progress: !!process.env.VUE_DEV_SERVER_PROGRESS } }