Here at SCS, we have been utilizing Visual Studio’s ability to create .NET Core web applications, bundled with Angular or React, for several years now. This has allowed us to get a web application project quickly and efficiently up and running. We can avoid spending a lot of time on the setup process.
Unfortunately, at the time of the writing of this blog, Visual Studio only offers Angular and React for bundled front-end frameworks when creating a .NET Core web application. It does not offer the ability to bundle what many consider to be the third major JavaScript framework, Vue.js, right out of the box. Compared to Angular and React, Vue.js is a performant, lightweight JavaScript framework that I have found works best for smaller projects. While it may not include as many features right out of the box as the other two, I have come to prefer the application structure and minimal nature of Vue.js. It does not feel like you are as locked into the patterns, and it has become my personal favorite JavaScript framework. Luckily, bundling Vue.js with a .NET Core web application manually isn’t too difficult a task, and that is exactly what this blog will cover!
Prerequisites
Before creating this web application, you must have the following installed, along with a basic understanding of using them:
- Visual Studio 2019 (with the ASP.NET web development and Node.js development workloads installed)
- The .NET Core SDK
- js with NPM
- A basic understanding of using the command prompt.
Creating the .NET Core Web App
To get started, we must create a blank .NET Core API web application.
- Open Visual Studio, then choose to create a new project, and select NET Core Web Application from the project templates.
- Name the project WebApplication and the overall solution VueApp.
- Select API as the project type.
Once your default API web application project has loaded, we need to remove the pre-generated files. Default API projects include a basic web page and controller as an example on how to create and call APIs. However, we do not need these for our Vue.js application, so we can discard them and remove the default properties associated with them.
- Remove the files WeatherForecast.cs, which is in the base layer of the project, and WeatherForecastController.cs, which is inside the Controllers folder.
- Open the file json, which is inside the Properties folder, and empty both launchUrl lines so the application will default to the home page again.
Your lauchSettings.json file should look like this once completed.
Adding the Vue.js Project Files
Now that we have the base of the web application created and modified, we can add in the Vue.js files to the project.
- Open a command prompt.
- Navigate to the project folder level.
- Enter the first NPM command in the screenshot below.
This will first install the Vue.js CLI globally, which is responsible for running, managing, and creating Vue applications. This may take a while.
- Once that is completed, you can then run the second NPM command in the above screenshot.
This command will create the Vue project files inside a folder called ClientApp at the base level of the project.
- Choose between Vue 2 or Vue 3.
Vue 3 is still in preview at the time of writing this blog. Choose whichever one you most feel comfortable with. If you have no preference, I suggest Vue 3, as I feel it is much-improved over Vue 2. When that command is finished, you should have all your Vue project files added to the web application project!
Integrating the Vue.js Project Files
With both the .NET Core web application project set up and the Vue.js project files created, the final steps revolve around integrating the two together into one seamless project.
The first step in this is downloading a NuGet package that includes extensions for creating SPA (single page application) services.
- In the top toolbar, navigate to Tools -> NuGet Package Manager -> Manage NuGet Packages for Solution.
- Search for the package AspNetCore.SpaServices.Extensions.
- Select your project.
- Select the latest 3.1.x version of the extension
- Click Install.
With that extension installed, we must now create a new class at the base level of the project called Connection.cs. This class is going to handle running the application in development by forwarding the application port to the Vue CLI server port on startup.This class should look as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.SpaServices; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.NetworkInformation; using System.Runtime.InteropServices; using System.Threading.Tasks; namespace WebApplication { public static class Connection { private static int Port { get; } = 8080; private static Uri DevServerEndpoint { get; } = new Uri($"http://localhost:{Port}"); public static void UserVueDevServer(this ISpaBuilder spa) { spa.UseProxyToSpaDevelopmentServer(async () => { if (IPGlobalProperties.GetIPGlobalProperties() .GetActiveTcpListeners() .Select(x => x.Port) .Contains(Port)) { return DevServerEndpoint; } var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var processInfo = new ProcessStartInfo { FileName = isWindows ? "cmd" : "npm", Arguments = $"{(isWindows ? "/c npm " : "")}run server", WorkingDirectory = "ClientApp", RedirectStandardError = true, RedirectStandardInput = true, RedirectStandardOutput = true, UseShellExecute = false }; var process = Process.Start(processInfo); var tcs = new TaskCompletionSource<int>(); _ = Task.Run(() => { try { string line; while ((line = process.StandardOutput.ReadLine()) != null) { if(!tcs.Task.IsCompleted && line.Contains("DONE Compiled successfully in")) { tcs.SetResult(1); } } } catch (EndOfStreamException ex) { tcs.SetException(new InvalidOperationException("'npm run serve' failed.", ex)); } }); var timeout = Task.Delay(TimeSpan.FromSeconds(60)); if(await Task.WhenAny(timeout, tcs.Task) == timeout) { throw new TimeoutException(); } return DevServerEndpoint; }); } } } |
With this class now created, we will modify the Startup.cs class to use both this previous class and the Vue.js project files. In the Startup.cs class, add the following methods below to make your class look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace WebApplication { 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) { // Add Vue files services.AddSpaStaticFiles(options => { options.RootPath = "ClientApp/dist"; }); // Add controllers services.AddControllers(); } // 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(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); // Add Vue files app.UseSpaStaticFiles(); app.UseSpa(spa => { spa.Options.SourcePath = "ClientApp"; if (env.IsDevelopment()) { spa.UserVueDevServer(); } }); } } } |
The three methods that were added will let the application know where the Vue.js files are located and that they should be used when building and running the application. Finally, the last method also checks that, if the current environment is the development environment, then use the custom class previously created to connect to the Vue CLI server.
With all this complete, your Vue.js and .NET Core web application should successfully compile and run! However, there is one task left to be completed. If you were to build and publish the application right now for deployment, the Vue.js project files would not be included in that publishing process. We must edit the WebApplication.csproj file itself to include this process. In your file, add the following sections to make your file look as such:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <SpaRoot>ClientApp\</SpaRoot> </PropertyGroup> <ItemGroup> <Folder Include="Controllers\" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.12" /> </ItemGroup> <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$SpaRoot)node_modules') "> <!-- Ensure Node.js is installed --> <Exec Command="node --version" ContinueOnError="true"> <Output TaskParameter="ExitCode" PropertyName="ErrorCode" /> </Exec> <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." /> <Message Importance="high" Text="Restoring dependecies using 'npm'. This may take several minutes..." /> <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" /> </Target> <Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish"> <!-- As part of publishing, ensure the JS resources are freshly build in production mode --> <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" /> <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" /> <!-- Include the newly-built files in the publish output --> <ItemGroup> <DistFiles Include="$(SpaRoot)dist\**" /> <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)"> <RelativePath>%(DistFiles.Identity)</RelativePath> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> </ResolvedFileToPublish> </ItemGroup> </Target> </Project> |
The first part added, the <SpaRoot> node under the main <PropertyGroup> node, is a variable that can be used throughout the file, and the value of that variable is the location of the Vue.js files. The second part added, the two new <Target> nodes towards the bottom, ensure that Node.js is installed before the application can be run, and that when publishing the application, the Vue.js project files are installed, built, and included in the published output.
And now that should be everything! Your application should be able to compile and run during development, and when published, all the necessary files for the application to run will be included. I hope this tutorial helps you set up your application and explore the wonders of Vue.js.
Happy Coding!