alexpotter.dev

Setting up a .NET 6 web app with Vue 3 and Vite

1 week ago | May 17, 2022 | 10 minute read

At the time of writing, creating a .NET 6 web application with a Vue 3 front-end is not as straightforward as it feels like it should be. There don’t seem to be any project templates you can use to get going quickly, and there doesn’t seem to be any good documentation on how you can set something up from scratch.

In this post I’ll explain how you can quickly and easily get a basic .NET 6 web application up and running with a modern Vue 3 front-end. I’ll assume that you have both the .NET Core 6.0 SDK and the NPM command-line tool installed.

Note that all the code below is available on GitHub via this sample application.

Step 1 - Scaffold the .NET project

The closest starting point we can use to scaffold our application is to use the React template. Open up a terminal and navigate to the directory you want your project folder to live in, then run the following:

dotnet new react -o MyProjectName

This creates a new project in the MyProjectName folder that includes a React application in the /ClientApp folder. In the next step we’ll remove this and replace it with a Vue 3 application.

Step 2 - Scaffold the Vue 3 project

The current officially recommended way to set up a Vue 3 project is to use Vite, so that’s what we’ll do.

First, delete the /ClientApp directory. Then, run the following command from your project directory:

npm create vite@latest

When prompted for the project name, enter ClientApp. Leave the package name as the recommended name, or feel free to enter a different name. For the framework, you should obviously select vue. Choose whichever variant you like, depending on whether you’d like to use TypeScript or not.

The Vite bootstrapper should create a new Vue 3 project in the /ClientApp folder.

Step 3 - Configure proxying

When developing locally, you will want to run your Vue application using the Vite dev server, because it gives you nice things like hot module reload (HMR). This means running your .NET application and your Vue application separately - in particular on different ports of localhost.

This isn’t ideal, because in production you’ll be serving your client and server from the same domain. Ideally we would be able to make calls to '/api/..' from our client code and not have to worry about where our server is hosted.

The latest .NET templates seem to have changed the way they’re set up to solve this problem. Rather than proxying requests through your .NET back-end to your front-end development server, they want you to do the opposite. I’m not entirely sure what motivated this decision, but I’m not keen on it.

Thankfully, it’s fairly easy to go back to the old way of doing things.

Remove the SPA Proxy package

First, go to your .NET project and open the Properties/launchSettings.json file. Remove both of the lines that read as follows:

"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"

Next, open up appsettings.Development.json and remove the following line from the Logging:LogLevel section:

"Microsoft.AspNetCore.SpaProxy": "Information",

Then, open up your .csproj file and remove the following lines:

  • <SpaProxyServerUrl>https://localhost:44476</SpaProxyServerUrl>
  • <SpaProxyLaunchCommand>npm start</SpaProxyLaunchCommand>
  • <PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="6.0.4" />

We’ve now removed all traces of the Microsoft.AspNetCore.SpaProxy NuGet package.

Add the SPA Extensions package

Next, install the latest stable version of the Microsoft.AspNetCore.SpaServices.Extensions NuGet package. Then, open up Program.cs and add the following towards the end of the file:

if (app.Environment.IsDevelopment()){    app.UseSpa(spa =>    {        spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");    });}else{    app.MapFallbackToFile("index.html");}
app.Run();

This adds a middleware to the request pipeline which forwards any unhandled requests on to our Vite development server (which will be running on http://localhost:3000).

Navigate to the /ClientApp folder and run the following command to start the development server:

npm run dev

Then, launch your .NET application. When it opens in the browser you should see your Vue application. However, you’ll notice that the page is constantly refreshing. What’s going on here?

Fix HMR

Well, the Vite dev server uses websockets to establish a connection with the browser for hot-module reload (HMR), However, because the Vite dev server by default runs over HTTP, the websockets requests are all using the unsecured protocol.

When these requests hit the back-end, the server automatically requests to upgrade to the secure websockets protocol. This appears to cause the page to reload, and the whole thing goes round again.

Option 1 - just use HTTP

The easiest way to fix this is to disable HTTPS redirection during development and just run over HTTP. To do this you’ll need to move the call to app.UseHttpsRedirection up into the block above it, like so:

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    // 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.UseHttpsRedirection();}

app.UseStaticFiles();
app.UseRouting();

Now if you restart the back-end and navigate to the .NET application’s HTTP URL, you should see the Vue application as before, but this time it’s not constantly refreshing. If you make a change to your Vue code, the page should almost instantaneously update in your browser thanks to HMR.

Running over HTTP in development might be perfectly fine for your needs, but in some cases you’ll need to be running over HTTPS. For example, if you’re integrating your application with an OAuth authorization server, you might only be allowed to configure HTTPS redirect URIs. In this case, you’ll need to configure the Vite development server to run over HTTPS.

Option 2 - run the Vite dev server over HTTPS

The easiest way to do this is to get hold of an SSL certificate for localhost in .pfx format and configure the Vite dev server to use that to secure the connection.

Given that we’re using .NET, we can take a slight shortcut. When you installed the .NET Core SDK, the ASP.NET Core HTTPS development certificate should have been automatically installed in your local user certificate store. Since it’s already there, we may as well take advantage of it.

Open the certificate manager by typing “certificates” into the Windows start menu and selecting Manage user certificates. From the Personal -> Certificates directory, locate the certificate with a “Friendly Name” of ASP.NET Core HTTPS development certificate. Right-click it, and go to All Tasks -> Export....

In the export wizard, do the following:

  • Opt to include the private key
  • Choose the .pfx format and leave the default options as they are
  • Secure the export with a simple password of your choice (e.g. secret)
  • For the export file location, choose /ClientApp/localhost.pfx

Note that .pfx files should automatically be ignored via the .gitignore included in the .NET template, but it’s worth double-checking that this file doesn’t get checked in to source control.

Note also that (if you haven’t already done so) you will need to trust the development certificate by running the following command in a terminal:

dotnet dev-certs https --trust

Now that we’ve got the certificate, we can configure Vite to use it to secure the development server. Open up the ClientApp/vite.config.ts file (or the equivalent .js file if you’re not using TypeScript) and add the following:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

import fs from 'node:fs';import type { ServerOptions } from 'https';
const developmentCertificateName = 'localhost.pfx';
const httpsSettings: ServerOptions = fs.existsSync(  developmentCertificateName)  ? {      pfx: fs.readFileSync(developmentCertificateName),      passphrase: 'secret',    }  : {};
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {    https: httpsSettings,  },});

(If you aren’t using TypeScript you will just want to remove the parts that reference the ServerOptions type.)

Note that the passphrase property should be set to the password you chose when exporting the certificate to .pfx format.

Now, go back to the Program.cs file of the .NET project and change the development server URL to use the HTTPS scheme:

if (app.Environment.IsDevelopment())
{
    app.UseSpa(spa =>
    {
        spa.UseProxyToSpaDevelopmentServer("https://localhost:3000");    });
}

Restart both the .NET back-end and the Vite development server, then open the HTTPS URL for the .NET back-end. You should see the Vue client application as before, but now it’s running over HTTPS. As before, if you make changes to the Vue code you should see things automatically update in your browser.

Configure endpoint routing

Now, there’s one last thing to mention, which is very important. Routing in ASP.NET Core uses a couple of middlewares, registered by calls to UseRouting and UseEndpoints:

  • UseRouting adds route matching to the middleware pipeline. This middleware looks at the set of endpoints defined in the app, and selects the best match based on the request.
  • UseEndpoints adds endpoint execution to the middleware pipeline. It runs the delegate associated with the selected endpoint.

What’s important to note is that UseRouting is not a Terminal Middleware, while UseEndpoints is. If you open up Program.cs and take a look, you’ll see that there isn’t a call to UseEndpoints. This is explained in the documentation as follows:

Apps typically don’t need to call UseRouting or UseEndpoints. WebApplicationBuilder configures a middleware pipeline that wraps middleware added in Program.cs with UseRouting and UseEndpoints. However, apps can change the order in which UseRouting and UseEndpoints run by calling these methods explicitly.

Essentially, UseEndpoints is added implicitly at the end of the middleware pipeline. In particular, its position in the middleware pipeline is after the call to UseProxyToDevelopmentServer.

This means that, as things stand, any requests we make that are intended for our back-end API will get swallowed up by the proxying behaviour, and will never hit our endpoint handlers. You can verify this by navigating to /WeatherForecast - you would expect to get a JSON response from the WeatherForecastController, but instead you’ll just see the Vue client.

To fix this, we just need to add an explicit call to UseEndpoints before we register the proxying middleware. Make the following change in Program.cs:

app.UseStaticFiles();
app.UseRouting();

app.UseEndpoints(endpoints =>{    endpoints.MapControllerRoute(        name: "default",        pattern: "{controller}/{action=Index}/{id?}"    );});
if (app.Environment.IsDevelopment())
{
    app.UseSpa(spa =>
    {
        spa.UseProxyToSpaDevelopmentServer("https://localhost:3000");
    });
}
else
{
    app.MapFallbackToFile("index.html");
}

Now if you navigate to /WeatherForecast you should get a JSON response from the API, as expected.

Conclusion

If you’ve run through all the steps above, you should now have a decent starting setup for developing a .NET 6 web app with a Vue 3 front-end.

If you have any comments or questions, please get in touch with me on Twitter.


Hi, I'm Alex. I use this blog to write about the random coding experiments I embark on in an attempt to learn new things.
Hit me up on Twitter @ajp_dev
© 2022 alexpotter.dev