Building the minimal ASP.NET Core MVC app with NPM and webpack
/ 8 min read
Update: this post is written using ASP.NET Core 1.1. With ASP.NET Core 2.0, things have gotten easier. See the updated version of this post for ASP.NET Core 2.0.
Recently, I was building a ASP.NET Core MVC app. Everything went smooth, except for the default ASP.NET Core Web application template. I think, it’s a bit bloated and it still uses Bower for managing client-side libs where the rest of the world uses NPM. It also leverages BundlerMinifier to bundle and minify client assets which lacks in functionality, especially compared to something like webpack, which I use quite a lot these days. Simply said: I just want to use NPM and webpack in my ASP.NET projects. So there I went again, ripping out and replacing things while thinking:
Wouldn’t it be easier to just start with the emptiest of templates and build from there?
This post reflects my findings while building a minimal ASP.NET Core MVC app from scratch with Visual Studio 2017 and is divided into the following parts:
- Step 0: Remove application insights
- Step 1: Add MVC, StaticFiles and configuration infrastructure
- Step 2: Add controller, views and a layout
- Step 3: Add client-side libs from NPM
- Step 4: Bundle with webpack
- Step 5: Publish
A little bit of knowledge of ASP.NET MVC and Visual Studio is assumed. If you already know how to create an ASP.NET Core MVC app from scratch or if you’re mainly interested in the NPM/webpack thing you might skip the first paragraphs and jump right to step 3.
The source code of the project is at https://github.com/martijnboland/MinimalAspNetCore/tree/netcore-1.1.
Starting from scratch
The goal of this exercise is to start with the empty ASP.NET Core template and add just enough to have a basic ASP.NET Core MVC app with a home page that is styled with Bootstrap and can be deployed to production environments.
Let’s start with the empty template:
resulting in the following project:
Step 0: Remove application insights
To start with a truly empty template we have to remove Application Insights from the project. No need for that right now. Also remove the line with .UseApplicationInsights() from Program.cs in the root of the project.
Step 1: Add MVC, StaticFiles and configuration infrastructure
Add the following NuGet packages:
- Microsoft.AspNetCore.Mvc
- Microsoft.AspNetCore.StaticFiles
Next, we’ll add the files appsettings.json and appsettings.Development.json to the root of the project. These files contain our logging configuration, together with other configuration options. See https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration for an overview of ASP.NET Core configuration and https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging for ASP.NET Core logging. The file Startup.cs is the place where everything is wired together:
At this point we have our base infrastructure in place for ASP.NET Core MVC pages. However, running the application, now results in a 404 not found error. What we need are a controller, some views and a layout.
As it turns out, this looks remarkably similar to the ASP.NET Core Web API template. An nice alternative is to take the Web API template as the starting point, and add the Microsoft.AspNetCore.StaticFiles NuGet package, together with adding app.UseStaticFiles() to Startup.cs.
Step 2: Add controller, views and a layout
for an introduction:
- Create folder ‘Controllers’ and add a file HomeController.cs with actions Index() and Error()
- Create folder ‘Views’ and add _ViewStart.cshtml (Add New Item –> MVC View Start Page) and _ViewImports.cshtml (Add New Item –> MVC View Imports Page)
- In the Views folder create the folders ‘Home’ and ‘Shared’
- Add ‘Index.cshtml’ to the ‘Views/Home’ folder and _Layout.cshtml and Error.cshtml to the ‘Views/Shared’ folder
The layout page (_Layout.cshtml) already has some bootstrap-specific markup:
This results in a working page without styles:
To make this page a bit prettier, we’re adding Bootstrap (and jQuery because the Bootstrap JavaScript plugins depend on jQuery).
Step 3: Add client-side libs from NPM
Bootstrap and jQuery are installed from the NPM repository. NPM is the package manager of Node.js. We have to make sure that a reasonably recent version of Node.js (version 6 or higher will do) is installed on our system and that it is on our path so we can run the node and npm commands from a console:
NPM requires a package.json file in the root of the project, so we’re adding that (Add New Item –> npm Configuration file, or from the command line in the root folder with ‘npm init’). After that, we’ll install our client libs with the following command (from the application root folder):
The libraries are installed, but how can we use these? The files are installed in the ‘node_modules’ folder, but we cannot simply reference those in our layout page, because scripts and css files need to be in the ‘wwwroot’ folder. What we need is a tool that bundles all required client-side scripts and styles and copies the bundle to the ‘wwwroot’ folder.
Step 4: Bundle with webpack
Webpack is becoming the standard for bundling and minification of client-side scripts, but it can do much, much more. Many people complain that setting up webpack is complex, but I honestly think that it is not. Let’s continue with our project.
For simplicity we’re going to create a single bundle that contains both our scripts and also our css styles. This way we only have to reference a single file in our layout page. The following code goes into the _Layout.cshtml file just before the tag (in Staging and Production environments, a version is added for cache busting):
Thus our goal becomes to create a single bundle ‘main.js’ that goes into the ‘wwwroot/dist’ folder.
Add webpack and some loaders to our project:
After that we can add the webpack configuration file to the root of our project (webpack.config.js):
The essential parts in the webpack configuration are the ‘entry’ and the ‘output’ fields. We’re basically saying: ‘analyze the entry file (‘Client/js/main’), resolve all references from there, put everything together in a single file and write that to the ‘wwwroot/dist’ folder with the name of the entry as file name (‘main.js’).
For the other config options, please check the webpack configuration reference and the loaders documentation to see what those are all about. It goes beyond the scope of this post to explain that all in detail.
. The file ‘Client/js/main.js’ contains:
There is no code in this file apart from some ES6-style imports. However, exactly these imports are used by webpack to determine what goes into the bundle. Also notice that our css file and the bootstrap css file are imported just as if they were JavaScript files.
Now we can create the bundle, but how do we instruct webpack to do that? The easiest way to do it is by adding commands to the scripts section of the NPM package.json file:
To create a development bundle enter:
To create a minified production bundle:
During development, it’s convenient that bundles are updated when something changes. This can be accomplished with:
After having created the bundle with one of the above commands we see this when we run the application:
Looks like Bootstrap doesn’t it? :)
Note: you can fully integrate the NPM commands in your Visual Studio workflow with the Task Runner Explorer and the NPM task runner extension.
Step 5: Publish
So there we have it. An ASP.NET Core MVC web application with NPM and webpack. Finally, we need something that creates our production bundle during publish. This can simply be done by just adding a few lines to the .csproj file:
After having published the production bundle is in the wwwroot/dist/ folder of the published web app.
And now?
Like this title already says: this is the minimal app. The source code is at https://github.com/martijnboland/MinimalAspNetCore/tree/netcore-1.1. There is some room for improvement. The first thing to do is to extract the css from the production bundle and move that to its own file to prevent the flicker when loading the app. Also, when the app grows it’s probably wise to extract the shared vendor libraries into its own bundle. But this is probably something for a follow-up post.