Customising Bootstrap in ASP .NET Core 2.2 Web Application Projects

The default Web Application template for .NET Core 2.2 in Visual Studio 2017 contains a default implementation of Bootstrap in the “wwwroot/lib” folder, which is fine if you are not concerned about your application/site being styled like every other Bootstrap site or including sizeable css and js files regardless of whether you are using all the features of Bootstrap or not.

My solution for a customised, trimmed down Bootstrap is to use npm and webpack to create your own custom bundle of Bootstrap css and js. My choice of npm is simply because I use this when developing frontend apps so it is familiar territory

Whilst this post is concerned with customising Bootstrap for use in a .NET Core app, the same technique can be used for other projects.

If you have not installed them already, ensure you have Visual Studio 2017 (Community or other), .NET Core SDK 2.2 and Node.js installed. You will also need the “ASP.NET and web development” workload installed within Visual Studio 2017.

Include “ASP.NET and web development” in Visual Studio 2017
Install .NET Core 2.2 SDK
Install node.js which includes npm

When you first create a .NET Core Web Application you will end up with a folder structure looking something like this:

Inital file list

Delete the “wwwroot/lib/bootstrap” folder and all its contents and sub-folders. We will not be using it anymore.

In Visual Studio 2017 add a “npm Configuration File” in the project’s root folder called package.json.

Add the npm config file

Note: When installing packages with npm we need to be in the project folder, not the solution folder (if different). Also, be careful you are not editing the npm config file when you install a package as npm will try to update the config file and may fail to do so if you have it locked.

Open a command prompt in the project’s root folder and issue the following commands to install the packages we are going to use:

npm install --save bootstrap popper.js jquery
npm install --save-dev webpack webpack-cli
npm install --save-dev sass-loader node-sass
npm install --save-dev babel-loader @babel/core @babel/preset-env
npm install --save-dev uglifyjs-webpack-plugin

Update the package.json file to have a scripts section with commands that we will use to build our bundled files, snip:

"scripts": {
    "bootstrap-js": "webpack --mode production --progress --profile --config webpack.bootstrap.js",
    "bootstrap-css": "node-sass --output-style compressed client/css/bootstrap.scss wwwroot/css/bootstrap.min.css",
    "bundles": "npm run bootstrap-js && npm run boostrap-css"

Create a “client” folder in the project’s root folder, and within the “client” folder create a “js” and “css” folder.

Create “client/css/_custom.scss” with the following test content (we can override any of the Bootstrap sass variables in this file to change the Bootstrap look and feel):

// Will change the backcolor of the body to yellow instead of white
$body-bg: #f9fbaf;

Create “client/css/bootstrap.scss” with the following test content:

// import our application specific bootstrap overrides in _custom.scss
@import "custom";
// import whole of bootstrap
@import "~bootstrap/scss/bootstrap";
// or just import the bits of bootstrap we will be using 
// (look in bootstrap's bootstrap.scss file for full list)
//@import "~bootstrap/scss/functions";
//@import "~bootstrap/scss/variables";
//@import "~bootstrap/scss/mixins";
//@import "~bootstrap/scss/root";
//@import "~bootstrap/scss/reboot";
//@import "~bootstrap/scss/type";
//@import "~bootstrap/scss/grid";
//@import "~bootstrap/scss/buttons";

Within “client/js” create this bootstrap.js file:

// include all of bootstrap javascript
//import 'bootstrap';
// or include just the bits of bootstrap javascript we want
import 'bootstrap/js/dist/modal';
import 'bootstrap/js/dist/tooltip';

Use Visual Studio 2017 to add the following javascript file in the project’s root folder; “webpack.bootstrap.js”:

// path is so we can resolve relative paths later regardless 
// of operating system differences
const path = require('path');
// uglifyjs-webpack-plugin will allow us to minify our js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
    entry: {
        // js entry point
        bootstrap: './client/js/bootstrap.js'
    // default mode
    mode: 'production',
    // apply rules
    module: {
        rules: [
                // use babel to transpile latest js into 
                // something earlier browsers understand
                // may not be needed depending on source
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
    optimization: {
        minimizer: [
            // js minification options
            new UglifyJsPlugin({
                cache: true,
                parallel: true,
                sourceMap: true
    output: {
        // output js and map filenames
        filename: 'js/[name].min.js',
        path: path.resolve(__dirname, 'wwwroot'),
        sourceMapFilename: 'js/[name]'
    // make sure a .map is created
    devtool: 'source-map'

You can now use the following at a command prompt in the project’s root folder to generate the initial file bundles:

npm run bundles

We can now undertake customisation, for instance, changing the colours and fonts used in bootstrap, perhaps only including the javascript for modals and tooltips, and only including a selection of the Bootstrap CSS that we will actually be using. This is achieved by editing the content of the “client/js/bootstrap.js”, “client/css/_custom.scss” and “client/css/bootstrap.scss” files, and then re-running “npm run bundles” to recreate our bundle files.

We will also need to update the “Pages/Shared/_Layout.cshtml” file to reference our bootstrap files rather than the bootstrap cdn and lib folder javascript and css files, snip:

    <link rel="stylesheet" href="~/css/bootstrap.min.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <script src="~/js/bootstrap.min.js" asp-append-version="true"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

Whether you need to add or remove other libraries will be entirely dependant on what you plan on delivering within the web application.

If you simply want to deliver the whole of the Bootstrap JS and only modify the CSS then including a reference to the full JS via a well known CDN is the best solution if the end users will have internet access as the full JS may already be cached in their browser from visits to other sites that reference it via the same CDN.

One further change you can make is to ensure the bundles are built every time you build your project. To do this select “Tools > Other Windows > Task Runner Explorer”. In the Task Runner Explorer you should see your package.jso file and the script items we created under “Custom”. Right-click on the “bundles” item, choose “Bindings” and then “Before Build”, now whenever you build your project the bundles will be built first. However, you may find the build takes a little too long for your liking, in which case just go back to building the Bootstrap files on demand.

If you want to use Azure DevOps to build the project and Bootstrap bundles as part of a CI pipeline there are a few steps you will need to take.

Currently, you will need to include the .NET Core 2.2 SDK as it is not ready in Azure DevOps automatically as of this date. It maybe available when you come to follow this article, in which case this first step can be skipped. Add a “.NET Core SDK Installer” step at the top of the pipeline like this:

You will also need to have the pipeline install npm and its dependencies, so add a npm step with the install command after the Restore step ensuring you select the correct working folder, similar to this:

You can then add a npm command to build your bundles by adding a further npm step with a custom command of “run bundles” (or whatever script command you setup), again ensuring the correct working folder is selected, similar to this: