Lean ASP.NET Core 2.1 - manually setup a Razor Pages project with Bootstrap, NPM and webpack
/ 10 min read
This post is part of a multi-post series:
- Lean ASP.NET Core 2.1 – manually setup a Razor Pages project with Bootstrap, NPM and webpack (this post)
- Lean ASP.NET Core 2.1 – add a React application to an existing Razor Pages application
- Lean ASP NET Core 2.1 – React forms, validation and Web API integration
In the past, I have written about setting up a minimal ASP.NET Core MVC application with NPM and webpack (ASP.NET Core 1.1 version, ASP.NET Core 2.0 version). These posts were mainly inspired by some unfortunate choices that had been made in the default Visual Studio project templates.
I tried to show an alternative way to manually setup an ASP.NET MVC project and use ‘modern’ commonly-used tools like webpack and NPM to create a simple ASP.NET MVC application with jQuery and Bootstrap for some client-side juice.
But isn’t this modern web development where everything changes all the time?
Yes :-). Fast-forward a few months and we’re in quite a different situation:
- ASP.NET Core 2.1 has been released with a lot of improvements and new features (for example, Razor Pages);
- The ASP.NET Core templates are improved significantly. Gone are Bower and things like bundleconfig.json. The simple templates just include the actual .js and .css files without requiring a package manager and the more complex SPA templates (angular, react) use NPM and webpack;
- Webpack 4 is released with more carefully chosen default settings, so less configuration is required;
So ditch the manual setup and pick a template?
Yes and no. In more situations than before, the default templates will now probably suffice, but on the other hand, I feel there’s something missing between the simple templates and the complex SPA templates.
Often, I want to start with a clean simple template but I also want to use NPM and webpack. There’s no template that covers this middle ground and the SPA templates are a bit overkill when you don’t have a SPA.
However, in this post I’ll show how easy it is to cover this middle ground with a manual setup. Starting with a simple template and manually add everything else step by step. I’m calling this a ‘lean’ application because we’re only adding the minimal required parts, but with keeping all options open for future extensions.
Building a ‘lean’ ASP.NET Core 2.1 application
Just like in the previous posts, we’re building a basic ASP.NET Core app with a home page that is styled with Bootstrap from NPM, built with webpack and can be deployed to production environments. For the impatient: get the source code at https://github.com/martijnboland/LeanAspNetCore ;-).
Prerequisites:
- .NET Core 2.1 SDK
- Node.JS 6.9.0 or higher
- Your favorite code editor, Visual Studio is not required but can also be used
- Some basic knowledge about ASP.NET, Razor, JavaScript
Since ‘lean’ is our objective we’re keeping it simple and don’t use our heavyweight friend Visual Studio but stick to the command line and a text editor. Everything works fine on Mac OS too.
First steps
-
Create a new empty ASP.NET Core project based on the webapi template. This project template serves as a nice clean starting point that has appSettings.json etc. setup but not too much garbage that needs to be deleted anyway:
-
Create a Pages folder in the folder where new project is created. Add the first Razor Page, Index.cshtml to the Pages folder:
-
Locate the Properties/launchSettings.json file and set the values of both launchUrl attributes to "":
-
Run the application:
-
Open your browser and browse to https://localhost:5001](https://localhost:5001). You should see the following:[
It works but it does look a bit basic don’t you think?
Styles, scripts and bundling
To make things look a bit less basic we’ll just add some Bootstrap styles. But also, it’s time to setup our client-side build infrastructure.
-
Create a ClientApp folder and install libraries there with NPM. The folder is named ClientApp simply because it’s called that way in the official SPA templates, so it will look already familiar for some of you.
With the above commands we created a package.json file, installed bootstrap with its dependencies (jquery and popper.js) and the build libraries (webpack with style and css loaders).
-
Add a css file in /ClientApp/styles/style.css. Here, we import the bootstrap css and add our own custom styles:
Note: the ~ in the import indicates that webpack should look in the node_modules folder.
-
Create an entry point for the client-side bundle. This is the JavaScript file that imports all dependencies. In our case it’s located in /ClientApp/js/main.js:
Note that the css file is also imported here.
-
Add a webpack configuration file (/ClientApp/webpack.config.js)
See the webpack documentation for the configuration options. The configuration file above means: ‘start building from the ./js/main.js file, write the results in the ../wwwroot/dist folder and process css files via the css-loader and style-loader’.
Webpack 4 has a new parameter: ‘mode’. The modes are ‘development’ and ‘production’ and webpack uses sensible default settings for the modes so you don’t have to configure these yourself anymore. See this post for more information.
-
Add a script command to package.json that executes the webpack build:
-
Build the client bundle (still from ClientApp folder):
After building, there is a single file ‘main.js’ in the /wwwroot/dist folder.
-
All we need to do now is to create a Razor partial _Header.cshtml file with a Bootstrap navigation bar and _Layout.cshtml file that includes the header file and add a script link to ~/dist/main.js:Noticed the environment tag helpers in the image above? We just include the main.js script file in the development environment but for the other environments, a version is appended to the file for cache busting. The layout file is set as the default layout for all pages via _ViewStart.cshtml in the Pages folder. To enable the tag helpers we must add a _ViewImports.cshtml file to the Pages folder:
For more information about Razor layouts, _ViewStart.cshtml and ViewImports.html, visit this page.
-
Add the StaticFiles middleware to Startup.cs just before app.UseMvc() so we can serve the js and other static files:
-
Go to the project root folder in the command line and execute:
When browsing to https://localhost:5001 you should now see a nice styled page:
That’s it! With a few steps we added Bootstrap from NPM and we’re able to include it in our application with the help of webpack.
Automate all the things
Having to execute ‘npm run build’ every time you want to see the results of a small change is getting tedious pretty fast . Luckily, ASP.NET Core already contains webpack development middleware (in Microsoft.AspNetCore.SpaServices.Webpack) that watches your client files and automatically rebuilds the bundle when a change happens. Change the Configure method in Startup.cs to:
As you can see, even hot module replacement is supported so the browser automatically reloads changed parts of your bundle without having to to a complete refresh. To make this happen we also need to add a few client libraries as well. Go to the ClientApp folder and execute:
Now, the development environment is setup for a nice development experience. Go to the project root folder and execute:
From now on, every time a file changes, either the server executable or the client bundle is rebuilt automatically. To see the effect, change the background color in ClientApp/styles/style.css, hit save and watch the browser.
Going live
One of the goals of this exercise is to create an optimized production-ready version of our application with a single command: ‘dotnet publish’.
First however, we need to add a little bit extra webpack configuration to extract the css from the main.js bundle to its own file. With the css is in the main bundle, you might have noticed some flickering because the css is loaded after the html and JavaScript (also because the script tag is at the bottom of the _Layout.cshtml page). We use the webpack MiniCssExtractPlugin to create the separate css bundle (from the ClientApp folder):
Change webpack.config.js to:
When the mode is ‘production’ (isProd), the MiniCssExtractPlugin loader is used instead of the style-loader that injects the css dynamically in the page. To load the extracted styles, a style tag is added to _Layout.cshtml:
Next, we need to add a ‘prod’ command to the scripts section in package.json to create a production bundle:
The ‘rimraf’ (rm –rf) command is just to clean any build artifacts from earlier builds. We don’t want those in our production bundles. Install it with npm (ClientApp folder):
Test if the production bundles are created by running:
You should now see a separate styles.css file in the wwwroot/dist folder and the total file size is much smaller than with a development build (npm run build).
How do we ensure that the production bundles are created when publishing the application? How can we hook into the publishing process? Edit the .csproj file in the project root folder:
A new target ‘BuildClientAssets’ is executed after ComputeFilesToPublish during the publishing process. Here we generate the client bundles by executing ‘npm run prod’. Also ‘npm install’ is called to ensure the libraries are installed. After generating the bundles, these are added to the files to publish. This step is copied directly from the ASP.NET SPA templates. Big thanks to the ASP.NET developers for figuring this out :-). Now publish:
Navigate to the publish output folder (/bin/Debug/netcoreapp2.1/publish), run the published application with ‘dotnet LeanAspNetCore.dll’ (the application .dll file) and navigate to https://localhost:5001. You should see the same application as before, but now as an optimized version.
Have a look at the page source in the browser and if things went right, you’ll see that the optimized .css and .js files are loaded separately and that a version querystring is added for cache busting.
Where to go from here?
The project we just created can serve as a foundation for ‘classic’ server-side ASP.NET /jQuery-style projects, but we also have everything in place to create a Single Page App or a hybrid variant.
An example of the first option can be found in the GitHub repository that contains the code for this post where an extra Form page is added with jQuery unobtrusive validation.
The next post will be an example of the second option as we add a mini React application to this project.