Using a Vue.js SPA with ASP.NET Core Web API

Using a Vue.js SPA with ASP.NET Core Web API

I recently started working on a new side project and decided to implement it as a single page application (SPA). For this, I chose to build an API powered by an ASP.NET Core Web API and a Vue.js powered front-end to consume the API.

I could find templates included in Visual Studio for AngularJS and React but nothing for Vue.js. Since I already have experience with Vue.js I decided to document the steps I used to create my starter project from a blank ASP.NET Core web app using ASP.NET Core 3.1 and Vue.js 2.

Install the required tools

We will need some command line tools installed to get started, these will be Node.js, the .NET CLI and the Vue CLI.

I have included links to the installation instructions for each tool below since these can vary a little depending on the platform. However, these are mostly running the appropriate installer or executing an NPM install command.

Now the tools are installed, we can move on to the next step! Getting your project setup.

Create a blank ASP.NET Core web app

I decided to start from a blank ASP.NET core web app. From here, I add the code required to support an ASP.NET Core Web API. 

To start go to your root project folder (I am going to use ExampleApp for mine) and then run the following command. 

dotnet new web

The .NET CLI will use the folder name as the name of the project. As a result, my project is also called ExampleApp.

At this point we have a pretty empty ASP.NET Core web app, so we will need to add the code to support an ASP.NET Core Web API.

Add support for ASP.NET Core Web API

The first thing you will notice missing if you are familiar with an ASP.NET Core MVC or ASP.NET Core Web API is the Controllers folder. This will need to be created as a place for us to put our controllers.

mkdir Controllers

Now that step is out of the way, we need to make some changes to the Startup.cs file.

The ConfigureServices method will need to be updated to register any controllers you create with the dependency injection container in ASP.NET Core. Thankfully, there is a useful extension method to make this simple. You just need to call the following method in ConfigureServices.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
}

The Configure method will need a little more work, but nothing too drastic. We need to map the endpoints of our API to the controllers and allow static files. We can make use of the provided extension methods to make this step easy too.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseStaticFiles();
    app.UseRouting();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Finally we can create a controller for us to test our Vue.js app with. I decided on a HelloWorld controller which I have provided below (this should go in the Controllers folder). But you can use any example you like.

using Microsoft.AspNetCore.Mvc;
namespace ExampleApp
{
    [ApiController]
    [Route("api/[controller]")]
    public class HelloWorldController : ControllerBase
    {
        [HttpGet]
        public string Get()
        {
            return "Hello World from ASP.NET Core Web API!";
        }
    }
}

If you execute dotnet run you should now have a working ASP.NET Core Web API project. To test this you can open {your root URL}/api/HelloWorld in your web browser and you should see the message “Hello World from an ASP.NET Core Web API!” displayed.

Now we need to add our Vue.js client app to consume the API.

Create the Vue.js client app

We will use the Vue CLI is create the Vue.js client app. For the example I will use the default options but you can select whichever you need.

You need to run the following command in the root project folder.

vue create clientapp

This will create a new Vue.js app called clientapp and place all the files in an appropriately named folder. If you don’t want to initialise a git repository you can add the flag shown below.

vue create clientapp --no-git

If you execute npm run serve from inside the clientapp folder you should be able to view the Vue.js app on the URL provided.

Now we have our API and Vue.js app working individually, we can combine them so that the Vue.js SPA and ASP.Net Core Web API are served from the same URL.

Add SPA support to ASP.NET Core Web API for Vue.js

ASP.NET Core Web API includes support for bundling an SPA as part of the API deployment. This allows the client SPA and the web API to be served from the same URL and keeps development simple for small projects.

The first step in this process is to install the Microsoft.AspNetCore.SpaServices.Extensions package. We can do this by running the following command in the root project folder.

dotnet add package Microsoft.AspNetCore.SpaServices.Extensions

When this has finished, we need to add a few entries to the .csproj file for our project.

Open ExampleApp.csproj and under the <TargetFramework> element add the following elements as shown in the example below.

<PropertyGroup>
  <TargetFramework>netcoreapp3.1</TargetFramework>
  <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
  <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
  <IsPackable>false</IsPackable>
  <SpaRoot>clientapp\</SpaRoot>
  <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
</PropertyGroup>

This above will let the .NET tools know where our SPA files are located and provide some additional details about how to treat the files during development.

Now we need to tell the .NET CLI how our SPA files should be handled during the publishing process. To do this we need to add the following <ItemGroup> and <Target> elements to the file.

<ItemGroup>
  <!-- Don't publish the SPA source files, but do show them in the project files list -->
  <Content Remove="$(SpaRoot)**" />
  <None Remove="$(SpaRoot)**" />
  <None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</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 dependencies 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 built 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>
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
    </ResolvedFileToPublish>
  </ItemGroup>
</Target>

After all of the above steps have been completed your .csproj file should look similar to the file below.

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
    <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
    <IsPackable>false</IsPackable>
    <SpaRoot>clientapp\</SpaRoot>
    <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.9" />
  </ItemGroup>
  <ItemGroup>
    <!-- Don't publish the SPA source files, but do show them in the project files list -->
    <Content Remove="$(SpaRoot)**" />
    <None Remove="$(SpaRoot)**" />
    <None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
  </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 dependencies 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 built 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>
        <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      </ResolvedFileToPublish>
    </ItemGroup>
  </Target>
</Project>

The final step is to let the ASP.NET Core Web API project know how load the SPA files when the project has been deployed. This can be done by using another helpful extension method in the Startup.cs file.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    // In production, the Vue files will be served from this directory
    services.AddSpaStaticFiles(configuration =>
    {
        configuration.RootPath = "clientapp/dist";
    });
}

We will also need to add a few more lines to the Configure method so that the middleware for serving SPA files is also active when the project has been deployed. This can go below app.UseStaticFles() as shown below.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseStaticFiles();
    if (!env.IsDevelopment())
    {
        app.UseSpaStaticFiles();
    }
    app.UseRouting();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

When you execute npm run serve the Vue CLI development server is launched. This provides you with a local URL which you can use to access your Vue.js site. Since we want to use the URL for the ASP.NET Core Web API project for all requests, the API will need to act as a proxy for the server during development.

To achieve this, we just need add the following method calls to the Configure method in the Startup.cs file. You can change the localhost address and port to match yours if it is different.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseSpa(spa =>
    {
        spa.Options.SourcePath = "clientapp";
        if (env.IsDevelopment())
        {
            spa.UseProxyToSpaDevelopmentServer("http://localhost:8080");
        }
    });
}

After that is done, you need to make sure your Vue CLI development server is running (if it isn’t, go to your clientapp folder and execute npm run serve to start it).

Now when you run your ASP.NET Core Web API and go to its URL you should see your Vue SPA!

Consume the ASP.NET Core Web API in the Vue.js client SPA

The final step is to consume our ASP.NET Core Web API in our Vue.js client SPA. To do this we will add a button to the sample application which will make a request to our API and update the page.

The first step is to install Axios. This is a HTTP client which we will use to make the HTTP request to the API. We can install it using NPM by running the following command in the clientapp folder.

npm install axios

Next, we need to update the App.vue file to import Axios, add a method to call the API and add a button for us to click. The full example for the file is below.

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" />
    <div>
      <button @click="fetch">Fetch from API</button>
    </div>
    <HelloWorld :msg="message" />
  </div>
</template>
<script>
import axios from "axios";
import HelloWorld from "./components/HelloWorld.vue";
export default {
  name: "App",
  data() {
    return {
      message: "Welcome to Your Vue.js App",
    };
  },
  methods: {
    async fetch() {
      try {
        const url = "http://localhost:5000/api/HelloWorld";
        const response = await axios.get(url);
        const data = response.data;
        this.message = data;
      } catch (error) {
        this.message = error;
      }
    },
  },
  components: {
    HelloWorld,
  },
};
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

You should now be able to run your ASP.NET Core Web API project (and make sure your Vue CLI development server is running too) and click the button to watch the heading be updated with the message from the API.

Summary

Your ASP.NET Core Web API project should be successfully proxying requests for the Vue CLI development server. In addition, the project should also be setup to publish your Vue.js client SPA files when you run dotnet publish.

This setup could be improved by adding a helper method for the Vue CLI development server, similar to the one built-in for Angular. I might take a look at implementing this in a future post.

You can find the complete example in this GitHub repository.

Hopefully this setup helps get your Vue.js and ASP.NET Core Web API projects off the ground!