Hotwired ASP.NET Core Web Application — Part 2

ipek
7 min readJun 18, 2022

--

Hotwired ASP.NET Core Web Application (6 Part Series)

Hotwired ASP.NET Core Web Application — Part 1 (Intro)
Hotwired ASP.NET Core Web Application — Part 2 (NPM, Webpack Setup)
Hotwired ASP.NET Core Web Application — Part 3 (Turbo Drive & Frame)
Hotwired ASP.NET Core Web Application — Part 4 (Turbo Stream)
Hotwired ASP.NET Core Web Application — Part 5 (Stimulus)
Hotwired ASP.NET Core Web Application — Part 6 (Quote Editor)

In the first part of this series, I have tried to summarize what Hotwire approach is and its components. Before porting “The Turbo Rails Tutorial” to ASP.NET Core, we should have to set up our development environment to use NPM and webpack. You can find the source code for the final application here.

I will use the latest .NET 6.0 on Visual Studio 2022 and create an ASP.NET Core Razor Pages web application named HotwiredQuoteEditor.

ASP.NET Core Web App

Now, we first have to set up our application workspace by removing unused libraries and adding the necessary ones.

Loading Packages

Instead of Bootsrap, we will use Tailwind CSS. We will also not use the client-side validation implemented with jQuery. So, we can remove the wwwroot\lib folder which contains the files for these libraries. After removing these folders, we should also remove their references from the Pages\Shared\_Layout.cshtml file.

Now, to integrate Turbo, Stimulus, Tailwind CSS, or any other outside package to our project, we will use NPM. Open a terminal window and go to the project root. We will create a folder named ClientApp under the project root and while inside that folder, we will initialize npm with npm init -y, and this will create an empty package.json file.

While in the ClientApp folder, we will install the following basic development dependencies that we’ll use later.

Basic Development Dependencies

Next, we will install the necessary development packages for Tailwind CSS.

Tailwind CSS Development Dependencies

We will run the following command to initialize Tailwind, which will create postcss.config.js and tailwind.config.js files.

npx tailwind init -p

Replace the tailwind.config.js with the following content:

Here the content section is important: it configures the paths to our HTML templates or any other source files that contain Tailwind class names. In an ASP.NET Razor Pages project, our Razor pages and supporting files can be found either in Pages or in the Areas directories. We also added the recommended @tailwindcss/forms plugin which provides a basic reset for form elements that are easy to override with utilities.

Also, modify the postcss.config.js to add the cssnano plugin which will minimize our CSS for the production build.

postcss.config.js with cssnano configuration

Lastly, create an app.css file under ClientApp/src/css folder. In that file, add the @tailwind directives for each of the Tailwind’s layers.

Tailwind CSS Directives in the app.css file

We have loaded all the necessary packages. Now, we need to configure our module bundler webpack, to generate the final (bundled, minified, obfuscated) JavaScript and CSS files or to bundle any other resources of our application.

Webpack Configuration

ClientApp folder already contains our package.json and tailwind config files (postcss.config.js and tailwind.config.js). We will also create our webpack.config.js file under this folder and replace its content with the following:

webpack.config.js

Inside webpack.config.js, we will set the context (the base directory for resolving entry points and loaders from the configuration) as ClientApp by context: path.join(__dirname, '.') . And relative to the context, we define an entry object with the app key pointing to ./src/js/app.js file. As we have previously created app.css under the ClientApp/src/css, now we will create an empty app.js file under the ClientApp/src/js. Webpack will use this file to start building the bundle.

With the output object, we set the path and filename where webpack will output our bundles and assets. Here, we set the output path as wwwroot\dist and the file name will be resolved as app.js.

By default, webpack is used to compile JavaScript modules. And other formats can be handled using loaders and connecting those with our directory structure. And under the module key, we define the rules that specify how each module will be created by specifying loaders or parsers. For example, for CSS file bundling, we apply three loaders and they are evaluated from right to left (last to first): First postcss-loader, then css-loader, and then MiniCssExtractPluginloader.

Now, we will add scripts to the scripts section of our package.json file to start our bundling process with webpack. By default, the scripts section contains one script named test. Replace the section with the following:

The scripts section of package.json

devbuild and prodbuild scripts are for building our modules in development and production environments respectively. But during development, we will mostly use the dev script, which has a watch option specified. By setting the watch, we instruct webpack to “watch” all files within the dependency graph for changes. When a file is updated, the code will be automatically recompiled, so that we won’t need to run the full build manually. However, the browser is not automatically refreshed for us to see the changes. To achieve this, instead of the watch option, we can use webpack-dev-server. But we will get automatic browser refresh using dotnet watch run, so using the watch option will be sufficient for us.

Now, before we run the devbuild script, remove everything from the wwwroot directory, except the favicon.ico file. While inside the ClientApp folder, run npm run devbuild in the terminal.

The wwwroot and ClientApp directory structure before and after building webpack

As we see from the image, the dist folder is created and inside it, a bundled app.js file is added. Also, our logo.png image is copied from the ClientApp/images to the dist\images folder using CopyWebpackPlugin. But, we do not see an app.css file under the dist folder. It’s because, although we have created the ClientApp\src\css\app.css file, we haven’t used it in our app.js file. “Using” it means “importing” it from a Javascript file. So, now when we add import '../css/app.css'; line inside our ClientApp\src\js\app.js file, and run npm run devbuild again, we will see that the app.css file is created under the dist folder with Tailwinds’ base styles. We should now reference these app.js and app.css files in the Pages\Shared\_Layout.cshtml file as in the following image:

Referencing app.js and app.css files

We set the defer attribute on our script tag, which specifies that it will be downloaded in parallel to parsing the page, and executed after the page has finished parsing.

Now that we have our JavaScript and CSS files bundling set up, it is time to run our project using dotnet watch which is a development tool that runs a .NET command to compile, run tests, or publish when source files change. Here, we want the project to be rebuilt when there is any change in our .NET related files.

By default, dotnet watch tracks all files matching the following glob patterns:

  • **/*.cs
  • *.csproj
  • **/*.resx
  • Content files: wwwroot/**, **/*.config, **/*.json

Remember, how the watch option wasn’t automatically refreshing the browser. But we see that dotnet watch is tracking the wwwroot folder, which is the output folder of our compiled JavaScript and CSS files. So, when we make a change in our source JavaScript and CSS files, they will be automatically compiled and outputted in the wwwroot folder and dotnet watch will refresh the browser.

So, everything is finally ready. Open two terminal windows in Visual Studio.

  • In the first terminal, go to the project root directory, and run dotnet watch run
  • In the second terminal, go to the ClientApp directory and run npm run dev

Our application will open in our default browser and we will see our rendered Index.cshtml page. At this point, we didn’t handle cache busting; so, to see the changes immediately, we should check Disable Cache on the Network tab of the Developer Tools window of our browser.

Disable cache to see the changes immediately

Since the project was using Bootstrap by default, we will not see any styling. Let’s change a few things and see that our hot reloads are working both on the .NET and webpack sides. In the following video, I showcased the webpack watch and its immediate application by adding Tailwind CSS classes to the body element in the Pages\Shared\_Layout.cshtml file. Also, removed the footer and Privacy links to trigger .NET hot reloads. You can watch the hot reloads in action in the following video.

Here is the final view of the Index page, after the changes in _Layout.cshtml and Index.cshtml.

Final index view

Finally, we are ready to work with Turbo in part 3. You can download the code covering this part from here.

References

[1] Introduction to Razor Pages in ASP.NET Core
[2] Webpack Concepts
[3] Tailwind CSS Documentation
[4] Cache busting JavaScript files using webpack and ASP.NET

--

--