Integrating ASP.NET Core and Vue.js

By Nick Kooman On February 10, 2020

Integrating ASP.NET Core and Vue.js
It’s hard enough trying to choose frameworks for your tech stack. It sometimes becomes frustrating when you find out they don’t integrate very easily. Recently I’ve had the opportunity to integrate ASP.NET Core and Vue.js so I thought I'd share my knowledge with you!

Creating our Solution

First, let’s create a Blank Solution. Within this solution, we will be creating two projects. Creating two separate projects allows a better separation of concerns between back-end and front-end code.

Example of creating a "Blank Solution"

Next, we will add an ASP.NET Core Web Application project and use the Web Application (Model-View-Controller) template.

Example of adding an ASP.NET Core Web Application project

Example of Web Application (Model-View-Controller) template

Finally, we create a Blank Node.js Web Application project.

Example of creating a Blank Node.js Web Application project

Our solution tree should look something like this:

Example of solution tree

Make sure to go to the properties of your solution and set the startup project to single and select the MVC project.

NPM Packages

Let’s install the packages we will need. Instead of installing packages manually through the command line, we will instead populate the package.json in our Node.js project and install all the packages in one go. Simply copy and paste the following code block, replacing the default code. Then run a clean install of the packages.
 
// package.json
{
   "name": "your-project-name-here",
   "version": "1.0.0",
   "description": "YourProjectNameHere",
   "scripts": {
       "build": "webpack --colors --progress",
       "watch": "webpack --colors --progress --watch"
   },
   "dependencies": {
       "@babel/polyfill": "^7.4.0",
       "@babel/runtime": "^7.7.4",
       "core-js": "^3.0.0",
       "vue": "^2.6.10"
   },
   "devDependencies": {
       "@babel/core": "^7.3.3",
       "@babel/plugin-proposal-class-properties": "^7.3.3",
       "@babel/plugin-proposal-decorators": "^7.4.0",
       "@babel/plugin-proposal-object-rest-spread": "^7.4.0",
       "@babel/preset-env": "^7.3.1",
       "babel-loader": "^8.0.5",
       "clean-webpack-plugin": "^2.0.1",
       "css-loader": "^2.1.1",
       "mini-css-extract-plugin": "^0.5.0",
       "node-sass": "^4.13.0",
       "sass-loader": "^7.3.1",
       "vue-loader": "^15.7.2",
       "vue-template-compiler": "^2.6.10",
       "webpack": "4.29.3",
       "webpack-cli": "3.2.3"
   }
}

Setting up our MVC Project

In our Index view of the Home feature, we can add a custom element. 
// Index.cshtml
<hello-world></hello-world>
This is where our matching Vue component will mount. 
 
Then in the layout, we can add some stylesheets and scripts that will be generated later by Webpack. In the head of our layout we will add these link tags:
// _Layout.cshtml
<link rel="stylesheet" href="~/dist/style.min.css" />
<link rel="stylesheet" href="~/dist/master.min.css" />
And at the end of our body will add these script tags:
// _Layout.cshtml
<script src="~/dist/vendor.min.js"></script>
<script src="~/dist/master.min.js"></script> 
That’s it for our MVC project! We’ve given our Vue application a home to live. Now we have to move in.

Structuring the Node.js Project

Let’s create a file, webpack.config.js, and a folder named src. Then, rename server.js to index.js and move it into the src folder. Within the src folder, create two more folders: components and styles. Finally, in the styles folder create style.scss.

Our Node.js Project should look something like this:

Example of Node.js Project

Creating and Mounting our Vue Components

Create a file named HelloWorld.vue in our components folder and insert the following code: 
// HelloWorld.vue
<template>
   <h1>Hello, world!</h1>
</template>

<script>
   export default {
       name: 'HelloWorld'
   }
</script>

<style lang="scss" scoped>
   h1 {
       color: red;
   }
</style>
In the same folder, create a JavaScript file named component-loader. First, let’s import Vue and our HelloWorld component.
// component-loader.js
import Vue from 'vue';
import HelloWorld from './HelloWorld.vue';
Second, let’s create an array of objects that contains our component and its corresponding custom element name.
// component-loader.js
const components = [
   {
       component: HelloWorld,
       element: 'hello-world'
   },
];
When you add more Vue components, all you will need to do is import the component and add another object with its reference and custom element name to the components array. I suggest translating from pascal-case to kebab-case as per Vue’s style guide, but ultimately the choice is up to you.

For example,
// component-loader.js
const components = [
   {
       component: HelloWorld,
       element: 'hello-world'
   },
   {
       component: NewComponent,
       element: 'new-component'
   }
]; 
Finally, let’s create and export the function that will render our Vue components.
// component-loader.js
export default {
   loadComponents() {
       components.forEach(({ component, element }) => {
           // Is the custom element in the DOM?
           if (!document.querySelector(element)) {
               return;
           }
 
           // Create a new Vue instance and mount it to the custom element.
           new Vue({
               render: createElement => createElement(component)
           }).$mount(element);
       });
   }
}

This function will take the components array and check if each custom element is present on the current page’s DOM. If it finds the element, it initializes and mounts the related Vue component. Once mounted, we’re in Vue land!

In index.js we want to import our component loader and call it.
// index.js
import ComponentLoader from './components/component-loader';

ComponentLoader.loadComponents(); 

Configure Webpack

Last but not least, we want to configure Webpack. Let’s add our constants. This includes plugin imports, paths, and babel configuration. For brevity, we’ll skip how this all works and just go through the setup.
// webpack.config.js
const path = require('path');

const CleanPlugin = require('clean-webpack-plugin');
const ExtractCssPlugin = require('mini-css-extract-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin'); 

const sourceDir = path.resolve(__dirname, './src');
const styleDir = path.resolve(sourceDir, './styles');
const buildDir = path.resolve('../YourProjectNameHere.MVC/wwwroot/dist');
 
const getSourcePath = srcPath => path.resolve(sourceDir, srcPath);

const babelLoader = {
   loader: 'babel-loader',
   options: {
       presets: [['@babel/preset-env', { modules: false, useBuiltIns: 'usage', corejs: 3 }]],
       plugins: [
           '@babel/plugin-proposal-object-rest-spread',
           ['@babel/plugin-proposal-decorators', { legacy: true }],
           '@babel/plugin-proposal-class-properties'
       ]
   }
};
Next, we insert our module.exports
// webpack.config.js
module.exports = {
   devtool: 'source-map',
   entry: {
       master: getSourcePath('index.js'),
       style: `${styleDir}/style.scss`
   },
   mode: 'development',
   module: {
       rules: [

           {
               test: /\.scss$/,
               use: [ExtractCssPlugin.loader, 'css-loader', 'sass-loader']
           },

           {
               exclude: /(node_modules|bower_components)/,
               include: sourceDir,
               test: /\.js?/,
               use: [{ ...babelLoader }]
           },

           {
               test: /\.css$/,
               use: [ExtractCssPlugin.loader, 'css-loader']
           },

           {
               test: /\.vue$/,
               loader: 'vue-loader'
           }
       ]
   },
   optimization: {
       splitChunks: {
           cacheGroups: {
               vendors: {
                   chunks: 'all',
                   name: 'vendor',
                   test: /[\\/]node_modules[\\/]/
               }
           }
       },
   },
   output: {
       filename: '[name].min.js',
       chunkFilename: '[name].min.js',
       globalObject: 'this',
       path: `${buildDir}`,
       publicPath: '/wwwroot/dist/'
   },
   plugins: [
       // Deletes build directory before rebuilds.
       new CleanPlugin({
           cleanOnceBeforeBuildPatterns: [`${buildDir}/**`],
           dangerouslyAllowCleanPatternsOutsideProject: true
       }),

       // Extract CSS from bundles, and compile them into a single CSS file
       new ExtractCssPlugin({
           filename: '[name].min.css'
       }),

       // Clone any other rules you have defined and apply them to the corresponding language blocks in .vue files.
       new VueLoaderPlugin()
   ],
};

If you have a different structure to your solution, you may need to tweak the path constants and entry points to suit your needs.

Final Remarks

With that, we’ve integrated MVC and Vue!

Once within a Vue entry point, you have all the great parts of Vue such as single-file components and reactive data. Adding more component entry points is easy too.

There is one caveat. Vue components have no elegant way to receive data from a Razor view. It is possible to send data through an API controller in the MVC project and make an AJAX call from your Vue components; however, we’ll have to cover that in a future article so stay tuned!

Share This Post:

Twitter Pinterest Facebook Google+
Click here to read more Tutorials posts
Start a Project with Us
Photo of the author, Nick Kooman

About the author

Nick began as an intern at BizStream, not even a year after graduating from high school, and is now a full-time developer! Nick thinks that growth stagnates when you are comfortable, and the best moments in life happen when you are uncomfortable. He believes he would not have had the chance to make a connection with BizStream if he had not stepped out of his comfort zone during school. When he's not working, Nick spends time with friends, plays video games, and watches YouTube.

View other posts by Nick

Subscribe to Updates

Stay up to date on what BizStream is doing and keep in the loop on the latest with Kentico.