<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lars Willemsens</title>
    <description>The latest articles on DEV Community by Lars Willemsens (@larswillemsens).</description>
    <link>https://dev.to/larswillemsens</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F598161%2Fdd65f85b-493b-44eb-b6f3-1bbcb99a14a2.png</url>
      <title>DEV Community: Lars Willemsens</title>
      <link>https://dev.to/larswillemsens</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/larswillemsens"/>
    <language>en</language>
    <item>
      <title>A quick introduction into Vite.AspNetCore</title>
      <dc:creator>Lars Willemsens</dc:creator>
      <pubDate>Mon, 02 Mar 2026 08:35:29 +0000</pubDate>
      <link>https://dev.to/larswillemsens/a-quick-introduction-into-viteaspnetcore-4d5n</link>
      <guid>https://dev.to/larswillemsens/a-quick-introduction-into-viteaspnetcore-4d5n</guid>
      <description>&lt;p&gt;If you want to build an MPA (Multi-Page Application) in ASP.NET Core with Vite then &lt;a href="https://github.com/Eptagone/Vite.AspNetCore" rel="noopener noreferrer"&gt;Vite.AspNetCore&lt;/a&gt; provides you with just the tools you need. The project is well-documented on its GitHub page and there are some very complete samples to base your project off of.&lt;br&gt;&lt;br&gt;
While testing out my setup, I gained some insights into how the library works and I made this little retrospective to document them.&lt;br&gt;&lt;br&gt;
Here's my project: &lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/aspnetvite" rel="noopener noreferrer"&gt;AspNetVite&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  First steps
&lt;/h1&gt;

&lt;p&gt;I started off by creating an ASP.NET Core MVC application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;AspNetVite
&lt;span class="nb"&gt;cd &lt;/span&gt;AspNetVite
dotnet new mvc
dotnet new gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I added the &lt;a href="https://www.nuget.org/packages/Vite.AspNetCore" rel="noopener noreferrer"&gt;NuGet package&lt;/a&gt; and updated my &lt;code&gt;Program.cs&lt;/code&gt; by following &lt;a href="https://github.com/Eptagone/Vite.AspNetCore?tab=readme-ov-file#setup" rel="noopener noreferrer"&gt;the project's setup instructions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then I copied some config files from &lt;a href="https://github.com/Eptagone/Vite.AspNetCore/tree/main/examples/net/ViteNET.MVC" rel="noopener noreferrer"&gt;one of the sample projects&lt;/a&gt; to my own project. These were: &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;tsconfig.json&lt;/code&gt;, &lt;code&gt;tsconfig.node.json&lt;/code&gt;, and (probably the most important one) &lt;code&gt;vite.config.ts&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
I customized &lt;code&gt;package.json&lt;/code&gt; (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt;, ...) and ran my first &lt;code&gt;npm i&lt;/code&gt;. You can see all changes I made in my commit log. These four Node-related files can be found in &lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/aspnetvite/-/commit/e577a26a4770e621d502ba6585b342f81965daf1" rel="noopener noreferrer"&gt;this commit&lt;/a&gt; or you can grab them from the sample project linked earlier.&lt;/p&gt;

&lt;p&gt;A big difference with the traditional ASP.NET MVC template is that Vite.AspNetCore uses an &lt;code&gt;Assets&lt;/code&gt; folder. In &lt;code&gt;vite.config.ts&lt;/code&gt;, you can see that Vite refers to &lt;code&gt;Assets/main.ts&lt;/code&gt; and that it will wipe your &lt;code&gt;wwwroot&lt;/code&gt;. ☠️&lt;br&gt;&lt;br&gt;
All TypeScript, Styling, and even the precious favicon had to make the move to &lt;code&gt;Assets&lt;/code&gt;. I created an &lt;code&gt;Assets/main.ts&lt;/code&gt; with a simple log statement and chose to do my styling with &lt;a href="https://sass-lang.com/" rel="noopener noreferrer"&gt;Sass&lt;/a&gt; so I created an &lt;code&gt;Assets/main.scss&lt;/code&gt; that I imported from the TypeScript file.&lt;br&gt;&lt;br&gt;
A TypeScript file can be imported in a view using the &lt;code&gt;vite-src&lt;/code&gt; tag-helper so I updated &lt;code&gt;_Layout.cshtml&lt;/code&gt; by adding this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;vite-src=&lt;/span&gt;&lt;span class="s"&gt;"~/main.ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and updated &lt;code&gt;_ViewImports.cshtml&lt;/code&gt; to enable the tag-helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@addTagHelper *, Vite.AspNetCore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(I also moved the &lt;code&gt;Scripts&lt;/code&gt; section to the &lt;code&gt;head&lt;/code&gt; element because we no longer live in the 2000s 😉.)&lt;br&gt;
&lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/aspnetvite/-/commit/650fad709a64b3868cdb74c29f1c2e59a2490866" rel="noopener noreferrer"&gt;Check out the changes here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There was still some code in &lt;code&gt;wwwroot&lt;/code&gt; that had to be moved over:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I moved styling from &lt;code&gt;site.css&lt;/code&gt; (and also from &lt;code&gt;_Layout.cshtml.css&lt;/code&gt;!) to &lt;code&gt;main.scss&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;I moved &lt;code&gt;favicon.ico&lt;/code&gt; to the &lt;code&gt;Assets&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;I removed the entire &lt;code&gt;wwwroot&lt;/code&gt; from my git repository.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code depends on Bootstrap and JQuery so instead of having them in the &lt;code&gt;wwwroot&lt;/code&gt;, I needed to download them through npm instead. In &lt;code&gt;package.json&lt;/code&gt;, I added these dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@popperjs/core"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.11.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jquery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.7.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jquery-validation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.21.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jquery-validation-unobtrusive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bootstrap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.3.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bootstrap-icons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.13.1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Assets/main.scss&lt;/code&gt; was then updated to import Bootstrap styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+@use "bootstrap/dist/css/bootstrap.css";
+@use "bootstrap-icons/font/bootstrap-icons.css";
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; h1.display-4 {
     background-color: #0f5132;
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and &lt;code&gt;Assets/main.ts&lt;/code&gt;, was updated to import Bootstrap's JavaScript code, JQuery, and other dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+import '@popperjs/core';
+import 'bootstrap';
+import 'jquery';
+// Using the next two lines is like including partial view _ValidationScriptsPartial.cshtml
+import 'jquery-validation';
+import 'jquery-validation-unobtrusive';
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; import './main.scss';
&lt;span class="err"&gt;
&lt;/span&gt; console.log('Hello, world!');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;_Layout.cshtml&lt;/code&gt; still referred to the old Bootstrap and JQuery, so it needed fixing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-&amp;lt;script type="importmap"&amp;gt;&amp;lt;/script&amp;gt;
-&amp;lt;link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /&amp;gt;
-&amp;lt;link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /&amp;gt;
-&amp;lt;link rel="stylesheet" href="~/AspNetVite.styles.css" asp-append-version="true" /&amp;gt;
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; &amp;lt;script type="module" vite-src="~/main.ts"&amp;gt;&amp;lt;/script&amp;gt;
 @await RenderSectionAsync("Scripts", required: false)
&lt;span class="gi"&gt;+&amp;lt;link rel="stylesheet" vite-href="~/main.scss" asp-append-version="true" /&amp;gt;
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; ...
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-&amp;lt;script src="~/lib/jquery/dist/jquery.min.js"&amp;gt;&amp;lt;/script&amp;gt;
-&amp;lt;script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"&amp;gt;&amp;lt;/script&amp;gt;
-&amp;lt;script src="~/js/site.js" asp-append-version="true"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was probably the biggest breaking change of the entire process. The full list of changes &lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/aspnetvite/-/commit/ce6cf1b929307d50cdafff126c47fba05ac1263b" rel="noopener noreferrer"&gt;can be seen here&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
Thankfully, I was rewarded with a first working version. (Hurray!)&lt;/p&gt;
&lt;h1&gt;
  
  
  Time to start cooking! 👨‍🍳
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/aspnetvite/-/commit/27158535453de0e0bc849a5d4469a0b2416f117d" rel="noopener noreferrer"&gt;Here&lt;/a&gt; you can see me try out Bootstrap Icons.&lt;br&gt;
This was easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;There should appear an 'archive' icon here: &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bi bi-archive"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(note that I imported Bootstrap Icons from &lt;code&gt;Assets/main.scss&lt;/code&gt; earlier)&lt;/p&gt;

&lt;p&gt;I also tested a Bootstrap popover &lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/aspnetvite/-/commit/2ef7c3fab488811e3bf622eaad3d856c487b8a64" rel="noopener noreferrer"&gt;as can be seen here&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
I had to add some HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-lg btn-danger"&lt;/span&gt; &lt;span class="na"&gt;data-bs-toggle=&lt;/span&gt;&lt;span class="s"&gt;"popover"&lt;/span&gt;
        &lt;span class="na"&gt;data-bs-title=&lt;/span&gt;&lt;span class="s"&gt;"Popover title"&lt;/span&gt;
        &lt;span class="na"&gt;data-bs-content=&lt;/span&gt;&lt;span class="s"&gt;"And here’s some amazing content. It’s very engaging. Right?"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click to toggle popover&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and a bit of TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; import '@popperjs/core';
&lt;span class="gd"&gt;-import 'bootstrap';
&lt;/span&gt;&lt;span class="gi"&gt;+import * as bootstrap from 'bootstrap';
&lt;/span&gt; import 'jquery';
 // Using the next two lines is like including partial view _ValidationScriptsPartial.cshtml
 import 'jquery-validation';
 import 'jquery-validation-unobtrusive';
&lt;span class="err"&gt;
&lt;/span&gt; import './main.scss';
&lt;span class="err"&gt;
&lt;/span&gt; console.log('Hello, world!');
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+/**
+ * https://getbootstrap.com/docs/5.3/components/popovers/#enable-popovers
+ */
+function enablePopovers() {
+    const popoverTriggerList = document.querySelectorAll('[data-bs-+toggle="popover"]');
+    Array.from(popoverTriggerList).map(popoverTriggerEl =&amp;gt; new +bootstrap.Popover(popoverTriggerEl));
+}
+
+enablePopovers();
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To validate the complete setup, I tested client-side validation. Since client-side validation is needed on certain views but not on others, this was the perfect time to split up the front-end logic. I created &lt;code&gt;Assets/validation.ts&lt;/code&gt; with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Importing this file is like including partial view _ValidationScriptsPartial.cshtml&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jquery-validation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jquery-validation-unobtrusive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Loaded validation.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And removed the validation code from &lt;code&gt;main.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; import '@popperjs/core';
 import * as bootstrap from 'bootstrap';
 import 'jquery';
&lt;span class="gd"&gt;-// Using the next two lines is like including partial view _ValidationScriptsPartial.cshtml
-import 'jquery-validation';
-import 'jquery-validation-unobtrusive';
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; import './main.scss';
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-console.log('Hello, world!');
&lt;/span&gt;&lt;span class="gi"&gt;+console.log('Loaded main.ts');
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I created a contact form, an action method in &lt;code&gt;HomeController&lt;/code&gt; (&lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/aspnetvite/-/commit/546eec393c1ef834652444268f5538afffe55d96" rel="noopener noreferrer"&gt;the whole nine yards&lt;/a&gt;), and imported the client-side validation code from my view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@section&lt;/span&gt; &lt;span class="n"&gt;Scripts&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="n"&gt;vite&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"~/validation.ts"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not only does this provide confirmation that everything works, but it's also a first step towards building a well-structured multi-page application.&lt;br&gt;&lt;br&gt;
In a multi-page application we want to have &lt;strong&gt;multiple bundles&lt;/strong&gt;. Each bundle can then be imported only on those pages that need it. A production application can quickly expand into dozens of bundles, many of which will be specific to a single page. Through the use of &lt;strong&gt;ECMAScript modules&lt;/strong&gt;, common code (such as utility functions or helper classes) can still be shared across bundles.&lt;/p&gt;
&lt;h1&gt;
  
  
  Moving to production
&lt;/h1&gt;

&lt;p&gt;Everything worked as long I was in "Development" mode, but "Production" was actually broken.&lt;br&gt;&lt;br&gt;
Three things needed fixing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vite config had to be fixed so that it creates multiple bundles, not just for &lt;code&gt;main.ts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;_Layout.cshtml&lt;/code&gt; a different kind of import  was needed for "Production" as opposed to "Development".&lt;/li&gt;
&lt;li&gt;From &lt;code&gt;csproj&lt;/code&gt;, I had to invoke &lt;code&gt;npm&lt;/code&gt; commands and specify which files are needed in the published &lt;code&gt;wwwroot&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To fix the bundles for production, I updated &lt;code&gt;vite.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; import { UserConfig, defineConfig } from 'vite';
&lt;span class="gi"&gt;+import fs from 'fs';
+import path from 'path';
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; export default defineConfig(async () =&amp;gt; {
&lt;span class="gi"&gt;+    const files = fs.readdirSync('./Assets');
+    const inputEntries = files
+        .filter(file =&amp;gt; file.endsWith('.ts'))
+        .reduce((acc, file) =&amp;gt; {
+            const fileName = path.parse(file).name;
+            acc[fileName] = path.join('./Assets', file);
+            return acc;
+        }, {} as Record&amp;lt;string, string&amp;gt;);
+
&lt;/span&gt;     const config: UserConfig = {
         appType: 'custom',
         root: 'Assets',
         publicDir: 'public',
         build: {
             emptyOutDir: true,
             manifest: true,
             outDir: '../wwwroot',
             assetsDir: '',
             rollupOptions: {
&lt;span class="gd"&gt;-                input: 'Assets/main.ts'
&lt;/span&gt;&lt;span class="gi"&gt;+                input: inputEntries
&lt;/span&gt;             },
         },
&lt;span class="err"&gt;
&lt;/span&gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's important to note that this configuration will treat all &lt;code&gt;.ts&lt;/code&gt; files directly under &lt;code&gt;Assets&lt;/code&gt; as "entries" (= files that trigger the creation of a new bundle). Code that shouldn't result in an additional bundle should be placed in a &lt;strong&gt;subdirectory&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;└── Assets
    ├── entry1.ts
    ├── entry2.ts
    ├── main.scss
    ├── main.ts
    ├── util
    │   ├── utility1.ts
    │   └── utility2.ts
    ├── presentation
    │   ├── presentation-logic1.ts
    │   └── presentation-logic1.ts
    └── services
        ├── service1.ts
        └── service2.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Due to the way Vite.AspNetCore works, I also had to change the way styles are picked up in production. In &lt;code&gt;_Layout.cshtml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-    &amp;lt;link rel="stylesheet" vite-href="~/main.scss" asp-append-version="true" /&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+    &amp;lt;environment include="Development"&amp;gt;
+        &amp;lt;link rel="stylesheet" vite-href="~/main.scss" asp-append-version="true" /&amp;gt;
+    &amp;lt;/environment&amp;gt;
+    &amp;lt;environment include="Production"&amp;gt;
+        &amp;lt;link rel="stylesheet" vite-href="~/main.ts" asp-append-version="true" /&amp;gt;
+    &amp;lt;/environment&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, you read that right... in production, styles are includes through &lt;code&gt;.ts&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Finally, I updated my &lt;code&gt;.csproj&lt;/code&gt; file and specified which files should be copied to &lt;code&gt;wwwroot&lt;/code&gt; and which (npm) commands are needed to build the application, both in development and in production.&lt;br&gt;
&lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/aspnetvite/-/commit/e667c1f25a3017d5a4d91226f054b24d55a1ab96?file_path=AspNetVite.csproj#933f644f7486b0ba08187ebf021e326a84cc39df" rel="noopener noreferrer"&gt;The changes to &lt;code&gt;.csproj&lt;/code&gt; can be seen here.&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Overall the experience was quite smooth, thanks to the existing sample applications. Once the project structure is migrated, everything "Just Worked™". I found the updates to &lt;code&gt;.csproj&lt;/code&gt; and the different way of importing styles in production to be the trickiest. In my opinion, these things weren't very clear from the documentation, but maybe I missed something. 😀&lt;/p&gt;

&lt;p&gt;I hope my findings can be of use to someone. Do let me know if that's the case. Just drop a comment below. Happy coding!&lt;/p&gt;

</description>
      <category>vite</category>
      <category>aspdotnet</category>
      <category>aspnet</category>
      <category>npm</category>
    </item>
    <item>
      <title>Building a multitenant web application with Spring Boot</title>
      <dc:creator>Lars Willemsens</dc:creator>
      <pubDate>Mon, 20 Mar 2023 19:54:27 +0000</pubDate>
      <link>https://dev.to/larswillemsens/building-a-multitenant-web-application-with-spring-boot-1pc8</link>
      <guid>https://dev.to/larswillemsens/building-a-multitenant-web-application-with-spring-boot-1pc8</guid>
      <description>&lt;p&gt;Let's create a web application to serve multiple different clients or to host a range of sub-sites and platforms.&lt;br&gt;&lt;br&gt;
Spring Boot will be our companion on this trip with JPA/Hibernate taking care of storage and persistence.&lt;/p&gt;

&lt;p&gt;Tenants must be disconnected from each other, and users should be able to register to and contribute to the different tenants' websites transparently. In short, users should be oblivious to the fact that the tenants' web applications are part of the one single Spring application.&lt;/p&gt;

&lt;p&gt;What this article will be focusing on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The web application experience. We want to ensure a maintainable application across different tenants' URLs, and security must be kept in check. Each tenant should have their own (sub)domain.&lt;/li&gt;
&lt;li&gt;We're using a &lt;a href="https://www.baeldung.com/multitenancy-with-spring-data-jpa" rel="noopener noreferrer"&gt;single database and shared schema&lt;/a&gt;. It allows us to reason about tenants on a design level and simplifies data source access. There are many articles and online sources that tackle &lt;a href="https://spring.io/blog/2022/07/31/how-to-integrate-hibernates-multitenant-feature-with-spring-data-jpa-in-a-spring-boot-application" rel="noopener noreferrer"&gt;the&lt;/a&gt; &lt;a href="https://callistaenterprise.se/blogg/teknik/2022/03/26/multi-tenancy-with-spring-boot-part7/" rel="noopener noreferrer"&gt;challenge&lt;/a&gt; &lt;a href="https://callistaenterprise.se/blogg/teknik/2022/03/26/multi-tenancy-with-spring-boot-part7/" rel="noopener noreferrer"&gt;of&lt;/a&gt; &lt;a href="https://medium.com/@konstde00/spring-boot-multi-tenant-architecture-overview-88198ea3991f" rel="noopener noreferrer"&gt;handling&lt;/a&gt; &lt;a href="https://github.com/sumanentc/multitenant" rel="noopener noreferrer"&gt;multiple&lt;/a&gt; &lt;a href="https://jomatt.io/how-to-build-a-multi-tenant-saas-solution-sample-app/" rel="noopener noreferrer"&gt;data&lt;/a&gt; &lt;a href="https://techblog.geekyants.com/multi-tenancy-using-spring-boot-shared-schema-subdomain-identifier-architecture" rel="noopener noreferrer"&gt;sources&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our POC will use subdomains of localhost such as &lt;code&gt;tenant1.localhost&lt;/code&gt; and &lt;code&gt;tenant2.localhost&lt;/code&gt; to mimic the actual experience.&lt;br&gt;
When I register an account at &lt;code&gt;tenant1.localhost&lt;/code&gt;, I won't (yet) have an account at &lt;code&gt;tenant2.localhost&lt;/code&gt;. In fact, as a user, I should have no clue that both tenants are even served by the same Spring application. I should be able to create an account at &lt;code&gt;tenant2&lt;/code&gt; using the same email address as for &lt;code&gt;tenant1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The application itself will have "posts" on it. For example, you can think of them as blog posts or news posts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unauthenticated users should be able to see all posts.&lt;/li&gt;
&lt;li&gt;Authenticated users should be able to contribute by adding new posts.&lt;/li&gt;
&lt;li&gt;Tenant administrators should be able to both add and delete posts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There will be a global administrator role as well. There are no plans to implement anything meaningful for the global administrator in this POC. It could be expanded by adding features to create new tenants or upgrading users to tenant administrators.&lt;/p&gt;

&lt;p&gt;Technically, we'll use Spring Web with MVC/Thymeleaf and a REST API. I've set up the project using Gradle, Spring Boot 3 (Spring 6), and PostgreSQL.&lt;/p&gt;

&lt;p&gt;The end result of this journey can be found &lt;a href="https://gitlab.com/kdg-ti/integration-4/guides/multitenancy" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring Security
&lt;/h2&gt;

&lt;p&gt;Spring Security will provide a foundation for authentication and authorization. Let's tag a &lt;code&gt;@Configuration&lt;/code&gt; class with &lt;code&gt;@EnableWebSecurity&lt;/code&gt; and provide a bean of type &lt;code&gt;SecurityFilterChain&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SecurityFilterChain&lt;/span&gt; &lt;span class="nf"&gt;filterChain&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpSecurity&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Static assets should fall outside of our security requirements, so we'll add matcher rules such as :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="n"&gt;antMatcher&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/js/**"&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;permitAll&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I'm assuming a basic knowledge of Spring Security. So far, nothing special yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tenant?... what tenant?
&lt;/h3&gt;

&lt;p&gt;All HTTP traffic to &lt;code&gt;tenant1.localhost&lt;/code&gt;, &lt;code&gt;tenant2.localhost&lt;/code&gt;, and &lt;code&gt;localhost&lt;/code&gt; will arrive at our Spring application (assuming port 8080). We need a way to distinguish between them and &lt;code&gt;OncePerRequestFilter&lt;/code&gt; will be our tool of choice. I'll create one and call it &lt;code&gt;TenantFilter&lt;/code&gt;. This request filter will grab the tenant from the URL and store it so that we can check it later when further processing the request (i.e., in a controller).&lt;br&gt;
Each web request will be handled by a separate thread, so an excellent place to store this little piece of data is Java's &lt;code&gt;ThreadLocal&lt;/code&gt;. (In the future, this should even be compatible with &lt;a href="https://www.baeldung.com/openjdk-project-loom" rel="noopener noreferrer"&gt;Project Loom's&lt;/a&gt; virtual threads.)&lt;/p&gt;

&lt;p&gt;One of only a few places in a Spring application where &lt;code&gt;static&lt;/code&gt; can be used meaningfully:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TenantContext&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentTenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentTenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ThreadLocal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getCurrentTenant&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;currentTenant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setCurrentTenant&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;currentTenant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// More getters/setters&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The filter will use the context class by calling its setters:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TenantFilter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;OncePerRequestFilter&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;TenantRepository&lt;/span&gt; &lt;span class="n"&gt;tenantRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TenantFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TenantRepository&lt;/span&gt; &lt;span class="n"&gt;tenantRepository&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tenantRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tenantRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doFilterInternal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                    &lt;span class="nc"&gt;FilterChain&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;ServletException&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getTenant&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tenantRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findBySlug&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;Tenant:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;orElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;NOT_FOUND&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// Attempted access to non-existing tenant&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;TenantContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCurrentTenant&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;TenantContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCurrentTenantId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;doFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;shouldNotFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestURI&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/webjars/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestURI&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/css/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestURI&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/js/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestURI&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;endsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".ico"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getTenant&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getServerName&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dotIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;indexOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dotIndex&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;substring&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dotIndex&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We must override two methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;doFilterInternal&lt;/code&gt;: This method's first -essential- responsibility is to determine if we want to continue processing this request. If the answer is yes, then we call &lt;code&gt;chain.doFilter&lt;/code&gt;. Not calling the chain means the end of the road for this request. The other task is to get the tenant's code (&lt;a href="https://stackoverflow.com/a/4357014" rel="noopener noreferrer"&gt;slug&lt;/a&gt;) from the request URL and to store it for later use. A repository is used to retrieve the ID of the tenant (handy for later!).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shouldNotFilter&lt;/code&gt;: tells Spring when this filter is relevant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We only want to support tenants that exist in our database as well as &lt;code&gt;null&lt;/code&gt; (=no tenant). That's why we've added a 404 check.&lt;/p&gt;

&lt;h3&gt;
  
  
  UserDetailsService
&lt;/h3&gt;

&lt;p&gt;The first place where we will &lt;strong&gt;read&lt;/strong&gt; this &lt;code&gt;ThreadLocal&lt;/code&gt; information is from a custom &lt;code&gt;UserDetailsService&lt;/code&gt;. The method to override here is &lt;code&gt;loadUserByUsername&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomUserDetailsService&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;UserDetailsService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CustomUserDetailsService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;userRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;UserDetails&lt;/span&gt; &lt;span class="nf"&gt;loadUserByUsername&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;UsernameNotFoundException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TenantContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCurrentTenant&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;loadUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;loadGeneralAdmin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;UserDetails&lt;/span&gt; &lt;span class="nf"&gt;loadUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findUser&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                                &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UsernameNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                                        &lt;span class="s"&gt;"'"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"' / '"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                                &lt;span class="s"&gt;"' was not found."&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;auths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GrantedAuthority&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;auths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimpleGrantedAuthority&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRole&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getRoleName&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CustomUserDetails&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEmail&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPassword&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTenant&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;auths&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;UserDetails&lt;/span&gt; &lt;span class="nf"&gt;loadGeneralAdmin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findGeneralAdmin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UsernameNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;"'"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"' was not found as a general admin."&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;auths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GrantedAuthority&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;auths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimpleGrantedAuthority&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ADMINISTRATOR&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRoleName&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CustomUserDetails&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEmail&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPassword&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;auths&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If the tenant is &lt;code&gt;null&lt;/code&gt;, we know the user is accessing &lt;code&gt;localhost&lt;/code&gt; without a subdomain. This part of our application will be used to host global administration pages.&lt;br&gt;
If the tenant is &lt;em&gt;not&lt;/em&gt; &lt;code&gt;null&lt;/code&gt;, then we must select a user filtering on &lt;strong&gt;both&lt;/strong&gt; email &lt;em&gt;and&lt;/em&gt; tenant.&lt;br&gt;
FYI, &lt;code&gt;userRepository.findGeneralAdmin&lt;/code&gt; filters by email &lt;em&gt;and&lt;/em&gt; tenant equal to &lt;code&gt;null&lt;/code&gt;.&lt;br&gt;
I've created a &lt;code&gt;CustomUserDetails&lt;/code&gt; class to extend Spring's &lt;code&gt;User&lt;/code&gt;. It contains the user ID and tenant ID as well. This will come in handy later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enabling the filter
&lt;/h3&gt;

&lt;p&gt;We can activate the request filter from within &lt;code&gt;WebSecurityConfig&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SecurityFilterChain&lt;/span&gt; &lt;span class="nf"&gt;filterChain&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpSecurity&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;
            &lt;span class="c1"&gt;// More config here...&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addFilterBefore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TenantFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenantRepository&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;UsernamePasswordAuthenticationFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;em&gt;before&lt;/em&gt;-part is essential here. &lt;code&gt;UserDetailsService&lt;/code&gt; will read from &lt;code&gt;TenantContext&lt;/code&gt;, so the filter must kick in &lt;em&gt;before&lt;/em&gt; authentication is done.&lt;/p&gt;

&lt;h2&gt;
  
  
  A controller Example
&lt;/h2&gt;

&lt;p&gt;Whenever someone creates a new post, the application needs to know two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who is the user (author) of this new post? Easy: we use &lt;code&gt;@AuthenticationPrincipal CustomUserDetails user&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Which tenant is this new post supposed to be a part of? Also easy: we call &lt;code&gt;TenantContext.getCurrentTenantId()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Like so:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"add_post"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@PreAuthorize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"isAuthenticated() &amp;amp;&amp;amp; !hasRole('ADMINISTRATOR')"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;addPost&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@AuthenticationPrincipal&lt;/span&gt; &lt;span class="nc"&gt;CustomUserDetails&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                      &lt;span class="nd"&gt;@Valid&lt;/span&gt; &lt;span class="nc"&gt;NewPostViewModel&lt;/span&gt; &lt;span class="n"&gt;postVm&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TenantContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCurrentTenantId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;postService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addPost&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postVm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"redirect:/posts"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;(&lt;code&gt;@PreAuthorize&lt;/code&gt; is part of &lt;a href="https://www.baeldung.com/spring-security-method-security" rel="noopener noreferrer"&gt;Spring Method Security&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;However, &lt;code&gt;TenantContext.getCurrentTenantId&lt;/code&gt; is way too much typing, and I'm way too lazy so...&lt;br&gt;
We create a custom annotation:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@Target&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ElementType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PARAMETER&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Retention&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RetentionPolicy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RUNTIME&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Documented&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nd"&gt;@interface&lt;/span&gt; &lt;span class="nc"&gt;TenantId&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;... a resolver:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TenantResolver&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;HandlerMethodArgumentResolver&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;supportsParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodParameter&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getParameterAnnotation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TenantId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                        &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getParameterType&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getTypeName&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"long"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="nf"&gt;resolveArgument&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MethodParameter&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ModelAndViewContainer&lt;/span&gt; &lt;span class="n"&gt;mavContainer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                  &lt;span class="nc"&gt;NativeWebRequest&lt;/span&gt; &lt;span class="n"&gt;webRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;WebDataBinderFactory&lt;/span&gt; &lt;span class="n"&gt;binderFactory&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;TenantContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCurrentTenantId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;... and activate it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebConfig&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;WebMvcConfigurer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;addArgumentResolvers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HandlerMethodArgumentResolver&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;argumentResolvers&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;argumentResolvers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TenantResolver&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we can do this(!):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"add_post"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@PreAuthorize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"isAuthenticated() &amp;amp;&amp;amp; !hasRole('ADMINISTRATOR')"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;addPost&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@TenantId&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                      &lt;span class="nd"&gt;@AuthenticationPrincipal&lt;/span&gt; &lt;span class="nc"&gt;CustomUserDetails&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                      &lt;span class="nd"&gt;@Valid&lt;/span&gt; &lt;span class="nc"&gt;NewPostViewModel&lt;/span&gt; &lt;span class="n"&gt;postVm&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;postService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addPost&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUserId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postVm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"redirect:/posts"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I've added support for &lt;code&gt;@Tenant String tenant&lt;/code&gt; parameters as well. Very convenient because &lt;code&gt;@ControllerAdvice&lt;/code&gt; can be used to add the tenant to the MVC model for &lt;em&gt;all&lt;/em&gt; controllers.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@ControllerAdvice&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GlobalControllerAdvice&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="no"&gt;LOGGER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GlobalControllerAdvice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="nd"&gt;@ModelAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tenant"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;populateTenantName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Tenant&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;On any of my Thymeleaf pages, I can now show the tenant:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;th:text=&lt;/span&gt;&lt;span class="s"&gt;"${tenant}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;... or test on it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;th:if=&lt;/span&gt;&lt;span class="s"&gt;"${tenant != null}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Check the full source &lt;a href="https://gitlab.com/kdg-ti/integration-4/guides/multitenancy" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-tenant security breaches
&lt;/h2&gt;

&lt;p&gt;The web application works fine at this stage, but unfortunately, it's not fully secure yet. Once a user authenticates, she'll be handed a cookie called "JSESSIONID" that could appear as if it's tenant-specific, but it isn't. When a request is made using this cookie, Spring Security will check the validity of this cookie without taking tenants into account. In fact, Spring Security doesn't know what a tenant is.&lt;/p&gt;

&lt;p&gt;Let's imagine I've authenticated against &lt;code&gt;tenant2&lt;/code&gt;, where I'm a tenant administrator. I've been given a cookie (yum!) that I will now use to make hand-crafted requests against &lt;code&gt;tenant1&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;

DELETE http://tenant1.localhost:8080/api/posts/1
Cookie: JSESSIONID=DF07D6D7C7CB9652830ABB3E108F20C7


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Without any additional checks, I'll be able to delete the posts of the other tenants. (CSRF has been left out of the equation in the example)&lt;/p&gt;

&lt;h3&gt;
  
  
  More filters
&lt;/h3&gt;

&lt;p&gt;This &lt;code&gt;TenantAuthorizationFilter&lt;/code&gt; compares the &lt;strong&gt;tenant of the request&lt;/strong&gt; (taken from the URL) against the &lt;strong&gt;tenant of the user&lt;/strong&gt; (taken from &lt;code&gt;CustomUserDetails&lt;/code&gt;).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TenantAuthorizationFilter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;OncePerRequestFilter&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="no"&gt;LOGGER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TenantAuthorizationFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doFilterInternal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                    &lt;span class="nc"&gt;FilterChain&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;ServletException&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TenantContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCurrentTenantId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;authentication&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SecurityContextHolder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContext&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getAuthentication&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authentication&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CustomUserDetails&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;authentication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPrincipal&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userTenantId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTenantId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userTenantId&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;doFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="no"&gt;LOGGER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warning&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Attempted cross-tenant access."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FORBIDDEN&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;shouldNotFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestURI&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/webjars/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestURI&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/css/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestURI&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/js/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestURI&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;endsWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".ico"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;When should this filter kick in?&lt;br&gt;
Indeed, &lt;strong&gt;after&lt;/strong&gt; authentication has happened (because &lt;code&gt;CustomUserDetails&lt;/code&gt; must be created first):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SecurityFilterChain&lt;/span&gt; &lt;span class="nf"&gt;filterChain&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpSecurity&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;
            &lt;span class="c1"&gt;// More config here...&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addFilterBefore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TenantFilter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenantRepository&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;UsernamePasswordAuthenticationFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addFilterAfter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TenantAuthorizationFilter&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;UsernamePasswordAuthenticationFilter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Speaking of cookies, my favorites are dinosaur coo--&lt;br&gt;
I mean, cookies are domain-specific inside of a browser. If you have a cookie for &lt;code&gt;tenant1.localhost&lt;/code&gt;, the browser will &lt;em&gt;not&lt;/em&gt; use it when making requests against &lt;code&gt;tenant2.localhost&lt;/code&gt;. That's great because tenants' websites will feel separated and disconnected, as they should be.&lt;br&gt;
That also means that the problem highlighted above &lt;em&gt;only&lt;/em&gt; applies to hand-crafted HTTP requests (Postman, &lt;code&gt;.http&lt;/code&gt; file, ...).&lt;/p&gt;

&lt;p&gt;Schematically, the situation that we've got ourselves into now looks like this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2v40b6y99evbw3k7jtr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2v40b6y99evbw3k7jtr.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling and customization
&lt;/h2&gt;

&lt;p&gt;A quick and easy way to give tenants their own customizable style would be to do something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/style"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StyleController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tenantStyle.css"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getTenantStyle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Tenant&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"body { background-color: #fcd2d2; }"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tenant1"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"body { background-color: #ebebfc; }"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tenant2"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"body { background-color: #edfceb; }"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Obviously, this is a &lt;em&gt;very&lt;/em&gt; hard-coded approach. Configurable colors could be stored in the database and retrieved from a service here. A &lt;code&gt;CssBuilder&lt;/code&gt; could be invented to do the heavy lifting, and caching could be added since the CSS won't change much.&lt;/p&gt;

&lt;h2&gt;
  
  
  Possible improvements and additions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;@Tenant&lt;/code&gt; parameter annotation could be made compatible with a custom &lt;code&gt;TenantDetails&lt;/code&gt; interface (similar to Spring Security's &lt;code&gt;UserDetails&lt;/code&gt;). This interface could have &lt;code&gt;getTenant&lt;/code&gt; and &lt;code&gt;getTenantId&lt;/code&gt; methods. The &lt;code&gt;TenantDetails&lt;/code&gt; class could be extended with a tenant name, logo (file path), etc.&lt;/li&gt;
&lt;li&gt;Error handling is quite basic right now. There's no form validation and error reporting. Redirecting after login success/failure has not been configured.&lt;/li&gt;
&lt;li&gt;Thymeleaf code could be cleaned up by moving the general administration navbar and pages into separate fragments and/or include files.&lt;/li&gt;
&lt;li&gt;For cleaner controller code, &lt;a href="https://www.baeldung.com/spring-security-method-security#5-method-security-meta-annotation" rel="noopener noreferrer"&gt;Method Security Meta-Annotations&lt;/a&gt; such as &lt;code&gt;@AdminOnly&lt;/code&gt; and &lt;code&gt;@TenantUserOnly&lt;/code&gt; can be created.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.baeldung.com/spring-cache-tutorial" rel="noopener noreferrer"&gt;Spring Caching&lt;/a&gt; can be used to cache the lookup of the tenant ID (inside &lt;code&gt;TenantFilter&lt;/code&gt;) and to cache calls to the &lt;code&gt;StyleController&lt;/code&gt; because they trigger &lt;em&gt;a lot&lt;/em&gt;! &lt;code&gt;@Cacheable&lt;/code&gt; is incredible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;As you know, all source code is &lt;a href="https://gitlab.com/kdg-ti/integration-4/guides/multitenancy" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
All multitenant specifics are covered above, but I recommend pulling the repository for a more complete picture. Do make sure to read the &lt;code&gt;README.md&lt;/code&gt; for instructions on setting up the database. All you need is docker and a JVM.&lt;/p&gt;

&lt;p&gt;I hope this writeup has given you the information you were looking for! Any comments are appreciated! Definitely let me know if you have any suggestions or recommendations.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>security</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Managing ASP.NET Core MVC front-end dependencies with npm and webpack (part 1)</title>
      <dc:creator>Lars Willemsens</dc:creator>
      <pubDate>Fri, 09 Apr 2021 12:23:37 +0000</pubDate>
      <link>https://dev.to/larswillemsens/managing-asp-net-core-mvc-front-end-dependencies-with-npm-and-webpack-part-1-3jf5</link>
      <guid>https://dev.to/larswillemsens/managing-asp-net-core-mvc-front-end-dependencies-with-npm-and-webpack-part-1-3jf5</guid>
      <description>&lt;p&gt;.NET 8 provides a couple of great project templates to get you up and running quickly. However, the MPA (multiple-page application) is not getting much attention. Front-end packages are outdated, and you can’t add new JS libraries using modern practices (&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/client-side/libman/?view=aspnetcore-8.0"&gt;LibMan&lt;/a&gt; doesn't count 😉).&lt;br&gt;
Starting out is easy though: either you choose the &lt;code&gt;web&lt;/code&gt; template, which gives you an almost empty project, or you can opt for more boilerplate by choosing the &lt;code&gt;mvc&lt;/code&gt; or &lt;code&gt;webapp&lt;/code&gt; template.&lt;/p&gt;

&lt;p&gt;Both have their (dis)advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dotnet new web&lt;/code&gt;: this gives you… pretty much
nothing. That’s not necessarily bad if 
you’re up for configuring everything from 
scratch.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dotnet new mvc&lt;/code&gt; (or &lt;code&gt;webapp&lt;/code&gt;): gives you an MPA
right off the bat, but the front-end libraries
are outdated and bundled &lt;em&gt;with&lt;/em&gt; the project,
which doesn’t provide a lot of room for change.
(&lt;code&gt;webapp&lt;/code&gt; results in a page-based project while 
&lt;code&gt;mvc&lt;/code&gt; produces a Model-View-Controller project)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting your front-end configured just the way you want can be challenging. There are no project templates that include a clean npm/webpack setup. If you want to use a SPA framework, you can choose between &lt;code&gt;angular&lt;/code&gt; and &lt;code&gt;react&lt;/code&gt;. But what if you use a different framework (such as &lt;a href="https://vuejs.org/"&gt;Vue&lt;/a&gt; or &lt;a href="https://svelte.dev/"&gt;Svelte&lt;/a&gt;!) or even no framework? Well, this guide can help you set that up!&lt;br&gt;
(Note: In .NET 8, the &lt;code&gt;react&lt;/code&gt; and &lt;code&gt;angular&lt;/code&gt; templates were migrated from the .NET SDK to Visual Studio.)&lt;/p&gt;
&lt;h3&gt;
  
  
  What we’re about to do
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;We’re going to set up an ASP.NET Core &lt;strong&gt;MVC&lt;/strong&gt;
project with custom front-end dependencies.
It’ll be a multi-page app that is tailored to 
our needs. More specifically…&lt;/li&gt;
&lt;li&gt;We’ll be making sure Bootstrap is a
&lt;em&gt;dependency&lt;/em&gt; of the project instead of a 
&lt;em&gt;part&lt;/em&gt; of the project. So, our project tree 
will no longer be cluttered with a bunch of 
minified JS and MAP files.&lt;/li&gt;
&lt;li&gt;We’ll be kicking out JQuery like it’s 2014!
It’ll only be referenced from ASP.NET’s form 
validation since &lt;a href="https://github.com/dotnet/aspnetcore/issues/8573"&gt;it still depends on it&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Using this setup, we'll be able to use &lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt;! 🎉&lt;/li&gt;
&lt;li&gt;The dependencies and build process will be set 
up using &lt;strong&gt;npm&lt;/strong&gt; and &lt;strong&gt;webpack&lt;/strong&gt;. Any code that we write will be processed by 
a build task. We’ll move all &lt;code&gt;.js&lt;/code&gt;, &lt;code&gt;.ts&lt;/code&gt;, and &lt;code&gt;.css&lt;/code&gt; 
files into a separate &lt;code&gt;src&lt;/code&gt; directory. Only the 
&lt;em&gt;build artifacts&lt;/em&gt; will end up in &lt;code&gt;wwwroot&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Why we’re doing it
&lt;/h3&gt;

&lt;p&gt;First and foremost, the MVC template is much too firm and inflexible. Adding new front-end libraries to the project is a bit of an undertaking and that shouldn’t be the case.&lt;br&gt;&lt;br&gt;
Adding a NuGet package to the project (thankfully) doesn’t result in a bunch of DLLs in the project tree. Neither should be the case when adding a library for the front-end. We also want control over the exact version numbers that are used, just like with NuGet packages.&lt;/p&gt;

&lt;p&gt;Along the way, we’ll get a better insight into how webpack can be used outside of a SPA!&lt;/p&gt;

&lt;p&gt;Let’s get to it!&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating the project
&lt;/h2&gt;

&lt;p&gt;What you’ll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://dotnet.microsoft.com/en-us/download/dotnet/8.0"&gt;.NET 8 SDK&lt;/a&gt;. Previous versions of .NET and .NET Core will work fine as well. I'm using 8.0.203 and have previously used .NET 5 and 6 as well as .NET Core for this setup.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/"&gt;NodeJS with bundled npm&lt;/a&gt;
(or yarn), I’m using 21.4.0.&lt;/li&gt;
&lt;li&gt;Optionally, an IDE. For now, all you need is a 
functional command line!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s create a new MVC project. Navigate to a new empty directory and enter the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ dotnet new mvc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’re using git then go ahead and create a &lt;code&gt;.gitignore&lt;/code&gt; file. Thankfully, there's a separate .NET template to take care of this. Still from within your project directory, enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ dotnet new gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Launch the project with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the output (or &lt;code&gt;Properties/launchSettings.json&lt;/code&gt;) to see which port number has been &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-6.0?view=aspnetcore-8.0#template-generated-ports-for-kestrel"&gt;generated&lt;/a&gt; for your project and navigate to &lt;code&gt;localhost&lt;/code&gt; to have a look.&lt;/p&gt;

&lt;p&gt;This startup project can be found on Gitlab as &lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/net8npmwebpack/-/tags/version-1"&gt;the first version&lt;/a&gt; of &lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/net8npmwebpack"&gt;Net8NpmWebpack&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Front-end dependencies
&lt;/h2&gt;

&lt;p&gt;Next up, create a new directory called &lt;code&gt;ClientApp&lt;/code&gt; at the root of your MVC project. Inside this new directory, create a file called &lt;code&gt;package.json&lt;/code&gt;. Give it the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"net8npmwebpack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ASP.NET Core MVC project with npm and webpack front-end configuration."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"repository"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://gitlab.com/kdg-ti/integratieproject-1/guides/net8npmwebpack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@popperjs/core"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.11.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"jquery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.7.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"jquery-validation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.20.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"jquery-validation-unobtrusive"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"bootstrap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.3.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"bootstrap-icons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.11.3"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"webpack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.91.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"webpack-cli"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.1.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"css-loader"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^6.10.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"style-loader"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.3.4"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webpack"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll want to change the values of the first five fields if you apply these steps to an existing project.&lt;/p&gt;

&lt;p&gt;Apart from the obvious fields, we’re specifying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dependencies&lt;/code&gt;: includes Bootstrap 5, JQuery,
and Popper (for popup placement, used by Bootstrap)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;devDependencies&lt;/code&gt;: webpack and webpack-related 
loaders. We’ll need these to bundle our front-end code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scripts&lt;/code&gt;: a single command that invokes the 
webpack bundling process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go ahead and run the following command from inside the &lt;code&gt;ClientApp&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new &lt;code&gt;node_modules&lt;/code&gt; directory will have popped up containing a massive amount of JS and CSS files. Consider this directory to be like the &lt;code&gt;bin&lt;/code&gt; and &lt;code&gt;obj&lt;/code&gt; directories (or, better yet, the NuGet package cache). Although it doesn’t contain any binaries, its content is still a load of dependencies we refer to from within our code.&lt;/p&gt;

&lt;p&gt;… but we don’t &lt;em&gt;have&lt;/em&gt; any code yet. Let’s add some by creating a &lt;code&gt;src&lt;/code&gt; directory inside &lt;code&gt;ClientApp&lt;/code&gt; and adding both a &lt;code&gt;js&lt;/code&gt; and a &lt;code&gt;css&lt;/code&gt; directory at that location. Create a new file called &lt;code&gt;site.js&lt;/code&gt; in the &lt;code&gt;js&lt;/code&gt; directory and &lt;strong&gt;move&lt;/strong&gt; the contents of &lt;em&gt;both&lt;/em&gt; &lt;code&gt;wwwroot/css/site.css&lt;/code&gt; and &lt;code&gt;Views/Shared/_Layout.cshtml.css&lt;/code&gt; to &lt;code&gt;ClientApp/src/css&lt;/code&gt;. Like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ClientApp/
├── package-lock.json
├── package.json
└── src
    ├── css
    │   └── site.css  &lt;span class="c"&gt;# contents of wwwroot/css/site.css and Views/Shared/_Layout.cshtml.css&lt;/span&gt;
    └── js
        └── site.js   &lt;span class="c"&gt;# newly created&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cleanup is important so take your time remove directories &lt;code&gt;wwwroot/css&lt;/code&gt; and &lt;code&gt;wwwroot/js&lt;/code&gt; entirely and to remove &lt;code&gt;Views/Shared/_Layout.cshtml.css&lt;/code&gt; as well.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;site.js&lt;/code&gt; we will write custom JavaScript code that’s relevant to the entire site. Additionally, we'll use this file to import all site-wide dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// JS Dependencies: Popper, Bootstrap &amp;amp; JQuery&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@popperjs/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bootstrap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jquery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Using the next two lines is like including partial view _ValidationScriptsPartial.cshtml&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jquery-validation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jquery-validation-unobtrusive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// CSS Dependencies: Bootstrap &amp;amp; Bootstrap icons&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bootstrap-icons/font/bootstrap-icons.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bootstrap/dist/css/bootstrap.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Custom JS imports&lt;/span&gt;
&lt;span class="c1"&gt;// ... none at the moment&lt;/span&gt;

&lt;span class="c1"&gt;// Custom CSS imports&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../css/site.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The &lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;site&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt; bundle has been loaded!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lines 2 to 9 will import code from within &lt;code&gt;node_modules&lt;/code&gt;. We’re importing Bootstrap’s Javascript, Popper, and CSS as well as a couple of JQuery libaries that are used by ASP.NET Core’s validation scripts.&lt;br&gt;
All of this is done using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import"&gt;ECMAScript 6 modules&lt;/a&gt;, a modern approach to importing JS files from other JS files or even CSS files from JS files.&lt;/p&gt;

&lt;p&gt;Now… how can we possibly get this code into a user’s browser? The file we just created contains only 17 lines of code, but it imports a small list of libraries and some custom CSS code. Do we just throw &lt;code&gt;node_modules&lt;/code&gt; at our users? Of course not! This directory is so large that we need to carefully filter, bundle, and host only those parts that are needed at runtime.&lt;/p&gt;

&lt;p&gt;… this is where webpack comes in!&lt;/p&gt;
&lt;h3&gt;
  
  
  Building the bundle
&lt;/h3&gt;

&lt;p&gt;Create a new file called &lt;code&gt;webpack.config.js&lt;/code&gt; and place it in the &lt;code&gt;ClientApp&lt;/code&gt; directory. Give it the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/js/site.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[name].entry.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wwwroot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;devtool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source-map&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;css$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;css-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.(&lt;/span&gt;&lt;span class="sr"&gt;png|svg|jpg|jpeg|gif|webp&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.(&lt;/span&gt;&lt;span class="sr"&gt;eot|woff&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;2&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;|ttf|otf|svg&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without going into too much detail, we’re specifying that a bundle should be created based on &lt;code&gt;src/js/site.js&lt;/code&gt; and that this bundle should be called &lt;code&gt;site.entry.js&lt;/code&gt; (the &lt;code&gt;[name]&lt;/code&gt; placeholder is replaced with “site”).&lt;/p&gt;

&lt;p&gt;webpack is smart enough to figure out what should be included in the bundle and bases its decision-making on what we do in &lt;code&gt;site.js&lt;/code&gt;. For example, if we import JQuery, then JQuery will be included in the bundle. Even CSS files such as those from Bootstrap will be included in the bundle if we choose to import them from within &lt;code&gt;site.js&lt;/code&gt;. (In the &lt;code&gt;rules&lt;/code&gt; section at the bottom, we specify how those non-JS files are handled.)&lt;/p&gt;

&lt;p&gt;Ok, let’s build this using the following command from within the &lt;code&gt;ClientApp&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, this executes the &lt;code&gt;webpack&lt;/code&gt; command as can be seen in the &lt;code&gt;scripts&lt;/code&gt; section of &lt;code&gt;package.json&lt;/code&gt; above.&lt;br&gt;&lt;br&gt;
This &lt;em&gt;build&lt;/em&gt; results in two new files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wwwroot/dist/
├── site.entry.js
└── site.entry.js.map
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can include those from within our HTML. Open &lt;code&gt;Views/Shared/_Layout.cshtml&lt;/code&gt; and replace all &lt;code&gt;script&lt;/code&gt; and &lt;code&gt;link&lt;/code&gt; tags (even those at the bottom!) with a single line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; &amp;lt;!DOCTYPE HTML&amp;gt;
 &amp;lt;html lang="en"&amp;gt;
 &amp;lt;head&amp;gt;
     &amp;lt;meta charset="utf-8" /&amp;gt;
     &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
     &amp;lt;title&amp;gt;@ViewData["Title"] - Net8NpmWebpack&amp;lt;/title&amp;gt;
&lt;span class="gi"&gt;+    &amp;lt;script src="~/dist/site.entry.js" defer&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;span class="gd"&gt;-    &amp;lt;link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /&amp;gt;
-    &amp;lt;link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /&amp;gt;
-    &amp;lt;link rel="stylesheet" href="~/Net8NpmWebpack.styles.css" asp-append-version="true" /&amp;gt;
&lt;/span&gt; &amp;lt;/head&amp;gt;
 &amp;lt;body&amp;gt;
     &amp;lt;header&amp;gt;
         &amp;lt;nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"&amp;gt;
             &amp;lt;div class="container-fluid"&amp;gt;
                 &amp;lt;a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index"&amp;gt;Net8NpmWebpack&amp;lt;/a&amp;gt;
                 &amp;lt;button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                         aria-expanded="false" aria-label="Toggle navigation"&amp;gt;
                     &amp;lt;span class="navbar-toggler-icon"&amp;gt;&amp;lt;/span&amp;gt;
                 &amp;lt;/button&amp;gt;
                 &amp;lt;div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"&amp;gt;
                     &amp;lt;ul class="navbar-nav flex-grow-1"&amp;gt;
                         &amp;lt;li class="nav-item"&amp;gt;
                             &amp;lt;a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index"&amp;gt;Home&amp;lt;/a&amp;gt;
                         &amp;lt;/li&amp;gt;
                         &amp;lt;li class="nav-item"&amp;gt;
                             &amp;lt;a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy"&amp;gt;Privacy&amp;lt;/a&amp;gt;
                         &amp;lt;/li&amp;gt;
                     &amp;lt;/ul&amp;gt;
                 &amp;lt;/div&amp;gt;
             &amp;lt;/div&amp;gt;
         &amp;lt;/nav&amp;gt;
     &amp;lt;/header&amp;gt;
     &amp;lt;div class="container"&amp;gt;
         &amp;lt;main role="main" class="pb-3"&amp;gt;
             @RenderBody()
         &amp;lt;/main&amp;gt;
     &amp;lt;/div&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt;     &amp;lt;footer class="border-top footer text-muted"&amp;gt;
         &amp;lt;div class="container"&amp;gt;
             &amp;amp;copy; 2024 - Net8NpmWebpack - &amp;lt;a asp-area="" asp-controller="Home" asp-action="Privacy"&amp;gt;Privacy&amp;lt;/a&amp;gt;
         &amp;lt;/div&amp;gt;
     &amp;lt;/footer&amp;gt;
&lt;span class="gd"&gt;-    &amp;lt;script src="~/lib/jquery/dist/jquery.min.js"&amp;gt;&amp;lt;/script&amp;gt;
-    &amp;lt;script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"&amp;gt;&amp;lt;/script&amp;gt;
-    &amp;lt;script src="~/js/site.js" asp-append-version="true"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;     @await RenderSectionAsync("Scripts", required: false)
 &amp;lt;/body&amp;gt;
 &amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;defer&lt;/code&gt; indicates that execution of this file should be delayed until the page is fully loaded. That’s a modern alternative to putting all &lt;code&gt;script&lt;/code&gt; tags at the bottom of a document to make sure that the document is fully downloaded before the scripts start scanning the DOM.&lt;/p&gt;

&lt;p&gt;While we're at it, let's quickly throw in a &lt;a href="https://icons.getbootstrap.com/"&gt;Bootstrap Icon&lt;/a&gt;. In &lt;code&gt;_Layout.cshtml&lt;/code&gt;, scroll down to the footer and change the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- &amp;amp;copy; 2024 - Net8NpmWebpack - &amp;lt;a asp-area="" asp-controller="Home" asp-action="Privacy"&amp;gt;Privacy&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+ &amp;lt;i class="bi bi-c-circle"&amp;gt;&amp;lt;/i&amp;gt; 2024 - Net8NpmWebpack - &amp;lt;a asp-area="" asp-controller="Home" asp-action="Privacy"&amp;gt;Privacy&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time to delete the last bit of clutter from our project tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ rm -rf wwwroot/lib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’re as obsessed with useless comments as I am then now’s the time to remove the comment in &lt;code&gt;site.css&lt;/code&gt;. Whatever minification reference is being made there, we’re not going down that path anyway.&lt;br&gt;&lt;br&gt;
And if you’re using git, add &lt;code&gt;wwwroot/dist/&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Alright, time to check it out!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;localhost&lt;/code&gt; (check that port again) in a browser and check the browser console (F12). You should see the message &lt;em&gt;“The ‘site’ bundle has been loaded!”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;How cool is that? All of the front-end’s dependencies are now downloaded, built, and bundled dynamically. The build system is extensible and all dependencies are easily managed and replaceable if needed!&lt;/p&gt;

&lt;p&gt;The project so far can be found on Gitlab as &lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/net8npmwebpack/-/tags/version-2"&gt;version 2&lt;/a&gt; of &lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/net8npmwebpack"&gt;Net8NpmWebpack&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript
&lt;/h2&gt;

&lt;p&gt;Now that we've done the heavy lifting, the world is our oyster! Let's add &lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
In &lt;code&gt;package.json&lt;/code&gt;, add a couple of &lt;code&gt;devDependencies&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;     "devDependencies": {
&lt;span class="gi"&gt;+        "@tsconfig/recommended": "^1.0.5",
+        "@types/bootstrap": "^5.2.10",
+        "ts-loader": "^9.5.1",
+        "typescript": "^5.4.3",
&lt;/span&gt;         "webpack": "^5.91.0",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;ClientApp&lt;/code&gt;, add a &lt;code&gt;tsconfig.json&lt;/code&gt; file with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"extends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@tsconfig/recommended/tsconfig.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"allowJs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rename your &lt;code&gt;js&lt;/code&gt; directory and &lt;code&gt;.js&lt;/code&gt; files. You should end up with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ClientApp/
└── src
    └── ts
        └── site.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, only webpack needs to be configured to handle TypeScript. In &lt;code&gt;webpack.config.js&lt;/code&gt; apply these changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;     entry: {
&lt;span class="gd"&gt;-        site: './src/js/site.js'
&lt;/span&gt;&lt;span class="gi"&gt;+        site: './src/ts/site.ts'
&lt;/span&gt;     },
     output: {
         filename: '[name].entry.js',
         path: path.resolve(__dirname, '..', 'wwwroot', 'dist'),
         clean: true
     },
     devtool: 'source-map',
     mode: 'development',
&lt;span class="gi"&gt;+    resolve: {
+        extensions: [".ts", ".js"],
+        extensionAlias: {'.js': ['.js', '.ts']}
+    },
&lt;/span&gt;     module: {
         rules: [
&lt;span class="gi"&gt;+            {
+                test: /\.ts$/i,
+                use: ['ts-loader'],
+                exclude: /node_modules/
+            },
&lt;/span&gt;             {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sass
&lt;/h2&gt;

&lt;p&gt;We can add &lt;a href="https://sass-lang.com/"&gt;Sass&lt;/a&gt; to make our styling life easier. 🕶️&lt;br&gt;&lt;br&gt;
In &lt;code&gt;package.json&lt;/code&gt;, we need two more &lt;code&gt;devDependencies&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;         "@types/bootstrap": "^5.2.10",
&lt;span class="gi"&gt;+        "sass": "^1.72.0",
+        "sass-loader": "^14.1.1",
&lt;/span&gt;         "ts-loader": "^9.5.1",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go for this directory structure and file name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ClientApp/
└── src
    └── scss
        └── site.scss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a kind of smoke test, we can introduce a Sass variable in &lt;code&gt;site.scss&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+ $button-bg-color: #1b6ec2;
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; // More styling here
&lt;span class="err"&gt;
&lt;/span&gt; .btn-primary {
   color: #fff;
&lt;span class="gd"&gt;-  background-color: #1b6ec2;
&lt;/span&gt;&lt;span class="gi"&gt;+  background-color: $button-bg-color;
&lt;/span&gt;   border-color: #1861ac;
 }
&lt;span class="err"&gt;
&lt;/span&gt; .nav-pills .nav-link.active, .nav-pills .show &amp;gt; .nav-link {
   color: #fff;
&lt;span class="gd"&gt;-  background-color: #1b6ec2;
&lt;/span&gt;&lt;span class="gi"&gt;+  background-color: $button-bg-color;
&lt;/span&gt;   border-color: #1861ac;
 }
&lt;span class="err"&gt;
&lt;/span&gt; // Even more styling follows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;site.ts&lt;/code&gt;, we must import the correct (renamed) style sheet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  // Custom CSS imports
&lt;span class="gd"&gt;- import '../css/site.css';
&lt;/span&gt;&lt;span class="gi"&gt;+ import '../scss/site.scss';
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, in &lt;code&gt;webpack.config.js&lt;/code&gt; we can enable Sass file processing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;             {
&lt;span class="gd"&gt;-                test: /\.css$/,
-                use: ['style-loader', 'css-loader']
&lt;/span&gt;&lt;span class="gi"&gt;+                test: /\.s?css$/,
+                use: ['style-loader', 'css-loader', 'sass-loader']
&lt;/span&gt;             },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give the application another spin. From &lt;code&gt;ClientApp&lt;/code&gt;, don't forget to install the new NPM dependencies and build a new bundle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;ClientApp
&lt;span class="nv"&gt;$ &lt;/span&gt;npm i
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run build
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;span class="nv"&gt;$ &lt;/span&gt;dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This concludes the first part of this guide. &lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/net8npmwebpack/-/tags/version-3"&gt;Here, you can find the third version of this project.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next?
&lt;/h2&gt;

&lt;p&gt;We’re not quite finished. There are two important things on our TODO list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance!&lt;/strong&gt; Try switching quickly between 
different pages in the SPA. You’ll see a delay 
before Bootstrap’s CSS gets applied. This is caused 
by a large amount of CSS getting plugged into 
the page at runtime. This is the most urgent 
thing to fix, &lt;em&gt;user experience&lt;/em&gt; is just too 
important!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building the project as a whole&lt;/strong&gt;. At this 
point building the .NET project doesn’t trigger 
a rebuild for the front-end. How annoying! :)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/larswillemsens/managing-asp-net-core-mvc-front-end-dependencies-with-npm-and-webpack-part-2-3acp"&gt;In part 2 we’ll be tackling the performance issue and streamlining the build process.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aspnet</category>
      <category>mvc</category>
      <category>webpack</category>
      <category>npm</category>
    </item>
    <item>
      <title>Managing ASP.NET Core MVC front-end dependencies with npm and webpack (part 2)</title>
      <dc:creator>Lars Willemsens</dc:creator>
      <pubDate>Fri, 09 Apr 2021 12:23:00 +0000</pubDate>
      <link>https://dev.to/larswillemsens/managing-asp-net-core-mvc-front-end-dependencies-with-npm-and-webpack-part-2-3acp</link>
      <guid>https://dev.to/larswillemsens/managing-asp-net-core-mvc-front-end-dependencies-with-npm-and-webpack-part-2-3acp</guid>
      <description>&lt;p&gt;This post is the second part of a two-part article on managing Javascript and CSS dependencies within a multiple-page application written in ASP.NET Core MVC.&lt;br&gt;
In &lt;a href="https://dev.to/larswillemsens/managing-asp-net-core-mvc-front-end-dependencies-with-npm-and-webpack-part-1-3jf5"&gt;the first part&lt;/a&gt;, we’ve specified our front-end dependencies, bumped the version numbers and set up a webpack build system.&lt;br&gt;
In this part, we’ll tackle performance issues and make sure that the entire project (front-end &lt;em&gt;and&lt;/em&gt; back-end) can be built using a single command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;While everything seems to be working okay, a few improvements must be made. When fast-clicking between pages (i.e., 'Home' and 'Privacy') you might notice that styles get applied &lt;em&gt;after&lt;/em&gt; the browser renders the page. That’s because the npm package &lt;code&gt;style-loader&lt;/code&gt; plugs the CSS into the page after it is loaded, causing the browser to re-render the page!&lt;br&gt;
On top of that, the Javascript bundle — which contains the CSS — is very large. It contains the entire Bootstrap CSS, some Bootstrap Javascript functions, and &lt;em&gt;all&lt;/em&gt; of JQuery!&lt;/p&gt;

&lt;p&gt;Let’s take care of this. Our aim is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bundling CSS into a separate CSS file that can be
statically referenced from an HTML &lt;code&gt;link&lt;/code&gt; tag&lt;/li&gt;
&lt;li&gt;Splitting up the JavaScript/TypeScript code into separate
bundles. Many pages have code that is unique to them,
and not every page needs JQuery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As mentioned in Part 1, we’re only keeping JQuery around because ASP.NET Core’s client-side form validation depends on it.&lt;/p&gt;

&lt;p&gt;To make sure that the solution that I’m about to present here fits all use cases, let’s set up a simple prototype. These are the pages we’re going to use:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faujjp72od3ylhal99bqr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faujjp72od3ylhal99bqr.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All the pages include the site’s SCSS and some code that is common to all pages. The individual pages are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Index&lt;/strong&gt; page, which has a bit of custom
TypeScript code running on it (&lt;code&gt;index.ts&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Privacy&lt;/strong&gt; page has no custom code. We'll give
it a Bootstrap component requiring specific Bootstrap code. This 
is similar to any other page
since the navbar has a JS pull-down feature on
small devices (the hamburger button). Like all pages, it needs Bootstrap’s styling to be rendered
correctly.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Contact&lt;/strong&gt; page has a form 
backed by ASP.NET Core’s form validation. Validation
can be done both server-side and client-side.
We'll load client-side validation code from &lt;code&gt;validation.ts&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With all of this in the pipeline you might be wondering why we went down this road in the first place. We will end up with individual CSS/JS files that are hard-referenced from the HTML pages… that’s what we started with! Well, sort of, but not quite. Here’s what’s different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We’re referencing our libraries with a specific version number&lt;/li&gt;
&lt;li&gt;The dependencies are not placed &lt;em&gt;inside&lt;/em&gt; the project tree&lt;/li&gt;
&lt;li&gt;We’ll end up with a performance gain (in the standard MVC template, all of JQuery is referenced from all pages)&lt;/li&gt;
&lt;li&gt;Our build system is extensible: Need an obscure npm dependency? No problem! Want to use the very latest ECMAScript features? You got it! Need minification or obfuscation? No problemo!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine what it would be like if this was already present in the standard MVC template. Then, you’d have all this modern front-end goodness without setting it up yourself. Ha!&lt;/p&gt;

&lt;p&gt;OK, let’s go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Splitting up the bundle
&lt;/h2&gt;

&lt;p&gt;This is the current state of things:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ ll wwwroot/dist/
total 3904
-rw-r--r-- 1 lars lars  176032 Apr  5 11:17 39795c0b4513de014cf8.woff
-rw-r--r-- 1 lars lars  130396 Apr  5 11:17 b7bcc075b395c14ce8c2.woff2
-rw-r--r-- 1 lars lars 1665226 Mar 31 12:21 site.entry.js
-rw-r--r-- 1 lars lars 2021658 Mar 31 12:21 site.entry.js.map


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That’s over 1600K worth of Javascript and CSS code.&lt;/p&gt;

&lt;p&gt;These are the separate blocks that we identified in the diagram above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sitewide styles (Bootstrap and custom SCSS)&lt;/li&gt;
&lt;li&gt;Sitewide JavaScript and TypeScript (this includes Bootstrap’s JS
code for fancy popup buttons, the navbar's hamburger, and so on)&lt;/li&gt;
&lt;li&gt;Validation scripts (basically JQuery with some extras):
for forms that use ASP.NET Core’s client-side form validation&lt;/li&gt;
&lt;li&gt;A sample Javascript code block that is unique to a
specific page (let’s take ‘Home’, so &lt;code&gt;index.ts&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what our view files look like after we move all of the common parts into &lt;code&gt;_Layout.cshtml&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvjt1e0d9wt5m304jesf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvjt1e0d9wt5m304jesf.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To split things up, we’ll dive into &lt;code&gt;ClientApp/src/ts/&lt;/code&gt; and turn &lt;code&gt;site.ts&lt;/code&gt; into three files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;site.ts&lt;/code&gt;: common code needed
on each page (includes Bootstrap). This will result in
&lt;strong&gt;separate&lt;/strong&gt; JS and CSS files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;validation.ts&lt;/code&gt;: JQuery, including the validation scripts
for our forms&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.ts&lt;/code&gt;: some dummy code that’s only applicable to ‘Home’&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s what they look like:&lt;/p&gt;

&lt;h4&gt;
  
  
  site.ts
&lt;/h4&gt;

&lt;p&gt;This file lost a few lines when compared to the previous version. Styling is needed on every page of the application, so we’re including all of our (S)CSS here:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@popperjs/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bootstrap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bootstrap-icons/font/bootstrap-icons.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bootstrap/dist/css/bootstrap.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Custom CSS imports&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../scss/site.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The &lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;site&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt; bundle has been loaded!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  validation.ts
&lt;/h4&gt;

&lt;p&gt;These import lines were previously in &lt;code&gt;site.ts&lt;/code&gt;. We’re putting them into their own file so that they can be included separately:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jquery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jquery-validation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jquery-validation-unobtrusive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The &lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;validation&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt; bundle has been loaded!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  index.ts
&lt;/h4&gt;

&lt;p&gt;… some dummy code:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The &lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;index&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt; bundle has been loaded!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Configuring the webpack build
&lt;/h3&gt;

&lt;p&gt;Separate files means separate &lt;a href="https://webpack.js.org/guides/code-splitting/" rel="noopener noreferrer"&gt;entries in webpack&lt;/a&gt;. Each entry is handled as a separate module, resulting in a separate Javascript file. The resulting file for each entry will be named after the entry followed by the &lt;code&gt;.entry.js&lt;/code&gt; suffix.&lt;br&gt;&lt;br&gt;
While we’re at it, we’ll extract the CSS from the Javascript bundle. Instead of using the &lt;code&gt;style-loader&lt;/code&gt; npm package, we’ll use &lt;code&gt;mini-css-extract-plugin&lt;/code&gt;, which takes care of the extraction.&lt;/p&gt;

&lt;p&gt;Brace yourself, &lt;code&gt;webpack.config.js&lt;/code&gt; is coming…&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt; const path = require('path');
&lt;span class="gi"&gt;+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; module.exports = {
     entry: {
&lt;span class="gd"&gt;-        site: './src/js/site.js'
&lt;/span&gt;&lt;span class="gi"&gt;+        index: './src/ts/index.ts',
+        site: './src/ts/site.ts',
+        validation: './src/ts/validation.ts'
&lt;/span&gt;     },
     output: {
         filename: '[name].entry.js',
         path: path.resolve(__dirname, '..', 'wwwroot', 'dist'),
         clean: true
     },
     devtool: 'source-map',
     mode: 'development',
     resolve: {
         extensions: [".ts", ".js"],
         extensionAlias: {'.js': ['.js', '.ts']}
     },
     module: {
         rules: [
             {
                 test: /\.ts$/i,
                 use: ['ts-loader'],
                 exclude: /node_modules/
             },
             {
                 test: /\.s?css$/,
&lt;span class="gd"&gt;-                use: ['style-loader', 'css-loader', 'sass-loader']
&lt;/span&gt;&lt;span class="gi"&gt;+                use: [{ loader: MiniCssExtractPlugin.loader }, 'css-loader', 'sass-loader']
&lt;/span&gt;             },
             {
                 test: /\.(png|svg|jpg|jpeg|gif|webp)$/i, 
                 type: 'asset'
             },
             {
                 test: /\.(eot|woff(2)?|ttf|otf|svg)$/i,
                 type: 'asset'
             }
         ]
&lt;span class="gd"&gt;-    }
&lt;/span&gt;&lt;span class="gi"&gt;+    },
+    plugins: [
+        new MiniCssExtractPlugin({
+            filename: "[name].css"
+        })
+    ]
&lt;/span&gt; };
&lt;span class="err"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;At the very top and the very bottom you can see we’re importing an npm package and adding it as a plugin respectively. Most plugins have a wide range of configuration options, but we only need to specify which filename to use for CSS files (&lt;code&gt;[name].css&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;entry&lt;/code&gt; section the different entries are defined and near the center of the file we’ve replaced &lt;code&gt;style-loader&lt;/code&gt; with the plugin.&lt;/p&gt;

&lt;p&gt;So one npm package is being replaced by another. Update &lt;code&gt;package.json&lt;/code&gt; accordingly:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt;     "devDependencies": {
         "@tsconfig/recommended": "^1.0.5",
         "@types/bootstrap": "^5.2.10",
         "sass": "^1.72.0",
         "sass-loader": "^14.1.1",
         "ts-loader": "^9.5.1",
         "typescript": "^5.4.3",
         "webpack": "^5.91.0",
         "webpack-cli": "^5.1.4",
         "css-loader": "^6.10.0",
&lt;span class="gd"&gt;-        "style-loader": "^3.3.4"
&lt;/span&gt;&lt;span class="gi"&gt;+        "mini-css-extract-plugin": "^2.8.1"
&lt;/span&gt;     },
&lt;span class="err"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let’s generate those new bundles. From the &lt;code&gt;ClientApp&lt;/code&gt; directory, enter:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ npm install
$ npm run build


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Which build artifacts were generated this time?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ ll ../wwwroot/dist/
total 2540
-rw-r--r-- 1 lars lars 176032 Apr  5 11:52 39795c0b4513de014cf8.woff
-rw-r--r-- 1 lars lars 130396 Apr  5 11:52 b7bcc075b395c14ce8c2.woff2
-rw-r--r-- 1 lars lars    295 Apr  5 11:52 index.entry.js
-rw-r--r-- 1 lars lars    246 Apr  5 11:52 index.entry.js.map
-rw-r--r-- 1 lars lars 387938 Apr  5 11:52 site.css
-rw-r--r-- 1 lars lars 506712 Apr  5 11:52 site.css.map
-rw-r--r-- 1 lars lars 290672 Apr  5 11:52 site.entry.js
-rw-r--r-- 1 lars lars 259544 Apr  5 11:52 site.entry.js.map
-rw-r--r-- 1 lars lars 363314 Apr  5 11:52 validation.entry.js
-rw-r--r-- 1 lars lars 467211 Apr  5 11:52 validation.entry.js.map


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  The Views
&lt;/h3&gt;

&lt;p&gt;After splitting the bundle into multiple smaller bundles, we now have to review our &lt;code&gt;link&lt;/code&gt; and &lt;code&gt;script&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;mini-css-extract-plugin&lt;/code&gt; in the picture, the CSS must be imported statically. CSS is used everywhere, so we jump into &lt;code&gt;_Layout.cshtml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt;     ...
&lt;span class="err"&gt;
&lt;/span&gt;     &amp;lt;title&amp;gt;@ViewData["Title"] - Net8NpmWebpack&amp;lt;/title&amp;gt;
     &amp;lt;script src="~/dist/site.entry.js" defer&amp;gt;&amp;lt;/script&amp;gt;
&lt;span class="gi"&gt;+    &amp;lt;link rel="stylesheet" href="~/dist/site.css"&amp;gt;
&lt;/span&gt; &amp;lt;/head&amp;gt;
 &amp;lt;body&amp;gt;
     &amp;lt;header&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt;     ...
&lt;span class="err"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Home/Index.cshtml&lt;/code&gt; page has custom code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt; @{
     ViewData["Title"] = "Home Page";
 }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+@section Scripts
+{
+    &amp;lt;script src="~/dist/index.entry.js" defer&amp;gt;&amp;lt;/script&amp;gt;
+}
+
&lt;/span&gt; &amp;lt;div class="text-center"&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt; ...
&lt;span class="err"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Privacy.cshtml&lt;/code&gt; page gets a fancy Bootstrap component. Let’s pick&lt;br&gt;
a &lt;a href="https://getbootstrap.com/docs/5.3/components/dropdowns/#single-button" rel="noopener noreferrer"&gt;dropdown menu button&lt;/a&gt;!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt; @{
     ViewData["Title"] = "Privacy Policy";
 }
 &amp;lt;h1&amp;gt;@ViewData["Title"]&amp;lt;/h1&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt; &amp;lt;p&amp;gt;Use this page to detail your site's privacy policy.&amp;lt;/p&amp;gt;
&lt;span class="gi"&gt;+
+&amp;lt;div class="dropdown"&amp;gt;
+    &amp;lt;button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"&amp;gt;
+        Dropdown button
+    &amp;lt;/button&amp;gt;
+    &amp;lt;ul class="dropdown-menu"&amp;gt;
+        &amp;lt;li&amp;gt;&amp;lt;a class="dropdown-item" href="#"&amp;gt;Action&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
+        &amp;lt;li&amp;gt;&amp;lt;a class="dropdown-item" href="#"&amp;gt;Another action&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
+        &amp;lt;li&amp;gt;&amp;lt;a class="dropdown-item" href="#"&amp;gt;Something else here&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
+    &amp;lt;/ul&amp;gt;
+&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;… and then there’s the Contact page, a new page we’ll build from scratch to test form validation. We’ll need a &lt;strong&gt;view&lt;/strong&gt;, a &lt;strong&gt;view-model&lt;/strong&gt;, some new &lt;strong&gt;actions&lt;/strong&gt; in the controller, and a &lt;strong&gt;link&lt;/strong&gt; on the site’s navigation bar.&lt;/p&gt;

&lt;p&gt;Let’s start with the form itself, a new view created as &lt;code&gt;Home/Contact.cshtml&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

@model ContactViewModel

@{
    ViewBag.Title = "Contact";
    Layout = "_Layout";
}

@section Scripts
{
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"~/dist/validation.entry.js"&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
}

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;@ViewBag.Title&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;asp-controller=&lt;/span&gt;&lt;span class="s"&gt;"Home"&lt;/span&gt; &lt;span class="na"&gt;asp-action=&lt;/span&gt;&lt;span class="s"&gt;"Contact"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"Subject"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"Subject"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;asp-validation-for=&lt;/span&gt;&lt;span class="s"&gt;"Subject"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"small text-danger"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"Message"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-label"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"Message"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;asp-validation-for=&lt;/span&gt;&lt;span class="s"&gt;"Message"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"small text-danger"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Subject&lt;/code&gt;, &lt;code&gt;Message&lt;/code&gt;, &lt;code&gt;ContactViewModel&lt;/code&gt;, … what are you on about!?&lt;br&gt;
Let’s move out of the &lt;code&gt;Views&lt;/code&gt; directory and into &lt;code&gt;Models&lt;/code&gt;…&lt;br&gt;
&lt;code&gt;ContactViewModel.cs&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel.DataAnnotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Net8NpmWebpack.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContactViewModel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;StringLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MinimumLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ErrorMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Please enter a message."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;… but the form is GET-ing and POST-ing all over the place, how is that handled?&lt;br&gt;
Time to edit &lt;code&gt;HomeController&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt;         ...
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+        [HttpGet]
+        public IActionResult Contact()
+        {
+            return View();
+        }
+
+        [HttpPost]
+        public IActionResult Contact(ContactViewModel contactVM)
+        {
+            if (ModelState.IsValid)
+            {
+                // Send an email or save the message in a table...
+                // Redirect to a page that says "Thanks for contacting us!"...
+
+                return RedirectToAction("Index");
+             }
+
+            return View();
+        }
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;        ...
&lt;span class="err"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;… ok and the link?&lt;br&gt;
Back to &lt;code&gt;_Layout.cshtml&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt; ...
&lt;span class="err"&gt;
&lt;/span&gt;                         &amp;lt;li class="nav-item"&amp;gt;
                             &amp;lt;a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy"&amp;gt;Privacy&amp;lt;/a&amp;gt;
                         &amp;lt;/li&amp;gt;
&lt;span class="gi"&gt;+                        &amp;lt;li class="nav-item"&amp;gt;
+                            &amp;lt;a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Contact"&amp;gt;Contact&amp;lt;/a&amp;gt;
+                        &amp;lt;/li&amp;gt;
&lt;/span&gt;                     &amp;lt;/ul&amp;gt;
                 &amp;lt;/div&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt; ...
&lt;span class="err"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Done! (almost)&lt;br&gt;
We now have a full-blown webpack and NPM-powered front-end with excellent performance and modern Javascript goodness.&lt;/p&gt;

&lt;p&gt;We don’t need &lt;code&gt;_ValidationscriptsPartial.cshtml&lt;/code&gt; anymore, so be sure to remove that one from your repository:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ rm Views/Shared/_ValidationScriptsPartial.cshtml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you’re consistently adding &lt;code&gt;defer&lt;/code&gt; to your script tags (and you should be! 🙂), you can go one step further and &lt;strong&gt;move&lt;/strong&gt; the &lt;code&gt;Scripts&lt;/code&gt; section inside &lt;code&gt;_Layout&lt;/code&gt; to that page’s &lt;code&gt;head&lt;/code&gt; section.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt; &amp;lt;!DOCTYPE HTML&amp;gt;
 &amp;lt;html lang="en"&amp;gt;
 &amp;lt;head&amp;gt;
     &amp;lt;meta charset="utf-8" /&amp;gt;
     &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
     &amp;lt;title&amp;gt;@ViewData["Title"] - Net8NpmWebpack&amp;lt;/title&amp;gt;
     &amp;lt;script src="~/dist/site.entry.js" defer&amp;gt;&amp;lt;/script&amp;gt;
&lt;span class="gi"&gt;+    @await RenderSectionAsync("Scripts", required: false)
&lt;/span&gt;     &amp;lt;link rel="stylesheet" href="~/dist/site.css"&amp;gt;
 &amp;lt;/head&amp;gt;
 &amp;lt;body&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt;     ...
&lt;span class="err"&gt;
&lt;/span&gt;     &amp;lt;footer class="border-top footer text-muted"&amp;gt;
         &amp;lt;div class="container"&amp;gt;
             &amp;lt;i class="bi bi-c-circle"&amp;gt;&amp;lt;/i&amp;gt; 2024 - Net8NpmWebpack - &amp;lt;a asp-area="" asp-controller="Home" asp-action="Privacy"&amp;gt;Privacy&amp;lt;/a&amp;gt;
         &amp;lt;/div&amp;gt;
     &amp;lt;/footer&amp;gt;
&lt;span class="gd"&gt;-    @await RenderSectionAsync("Scripts", required: false)
&lt;/span&gt; &amp;lt;/body&amp;gt;
 &amp;lt;/html&amp;gt;
&lt;span class="err"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The project so far can be found on GitLab as &lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/net8npmwebpack/-/tags/version-4" rel="noopener noreferrer"&gt;version 4&lt;/a&gt; of &lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/net8npmwebpack" rel="noopener noreferrer"&gt;NetCoreNpmWebpack&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
Give it a spin. You’ll notice that the performance is good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the project
&lt;/h2&gt;

&lt;p&gt;Running the project is perhaps easier said than done. Let’s recap:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 bash
$ npm install          # only after a modification to package.json
$ npm run build
$ dotnet build
$ dotnet run


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That’s too much typing for anyone. Let’s automate that a bit.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.csproj&lt;/code&gt; file can be extended with some extra build commands. Honestly, csproj-Hocus Pocus is a bit of uncharted territory for me (although it reminds me of the &lt;a href="https://en.wikipedia.org/wiki/Apache_Ant" rel="noopener noreferrer"&gt;Ant build system&lt;/a&gt;), but this seems to work fine:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt; &amp;lt;Project Sdk="Microsoft.NET.Sdk.Web"&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt;     &amp;lt;PropertyGroup&amp;gt;
         &amp;lt;TargetFramework&amp;gt;net8.0&amp;lt;/TargetFramework&amp;gt;
         &amp;lt;Nullable&amp;gt;disable&amp;lt;/Nullable&amp;gt;
         &amp;lt;ImplicitUsings&amp;gt;enable&amp;lt;/ImplicitUsings&amp;gt;
&lt;span class="gi"&gt;+        &amp;lt;IsPackable&amp;gt;false&amp;lt;/IsPackable&amp;gt;
+        &amp;lt;MpaRoot&amp;gt;ClientApp\&amp;lt;/MpaRoot&amp;gt;
+        &amp;lt;WWWRoot&amp;gt;wwwroot\&amp;lt;/WWWRoot&amp;gt;
+        &amp;lt;DefaultItemExcludes&amp;gt;$(DefaultItemExcludes);$(MpaRoot)node_modules\**&amp;lt;/DefaultItemExcludes&amp;gt;
&lt;/span&gt;     &amp;lt;/PropertyGroup&amp;gt;
&lt;span class="gi"&gt;+
+    &amp;lt;ItemGroup&amp;gt;
+        &amp;lt;PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.3"/&amp;gt;
+    &amp;lt;/ItemGroup&amp;gt;
+
+    &amp;lt;ItemGroup&amp;gt;
+        &amp;lt;!-- Don't publish the MPA source files, but do show them in the project files list --&amp;gt;
+        &amp;lt;Content Remove="$(MpaRoot)**"/&amp;gt;
+        &amp;lt;None Remove="$(MpaRoot)**"/&amp;gt;
+        &amp;lt;None Include="$(MpaRoot)**" Exclude="$(MpaRoot)node_modules\**"/&amp;gt;
+    &amp;lt;/ItemGroup&amp;gt;
+
+    &amp;lt;Target Name="NpmInstall" BeforeTargets="Build" Condition=" !Exists('$(MpaRoot)node_modules') "&amp;gt;
+        &amp;lt;!-- Ensure Node.js is installed --&amp;gt;
+        &amp;lt;Exec Command="node --version" ContinueOnError="true"&amp;gt;
+            &amp;lt;Output TaskParameter="ExitCode" PropertyName="ErrorCode"/&amp;gt;
+        &amp;lt;/Exec&amp;gt;
+        &amp;lt;Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE."/&amp;gt;
+        &amp;lt;Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..."/&amp;gt;
+        &amp;lt;Exec WorkingDirectory="$(MpaRoot)" Command="npm install"/&amp;gt;
+    &amp;lt;/Target&amp;gt;
+
+    &amp;lt;Target Name="NpmRunBuild" BeforeTargets="Build" DependsOnTargets="NpmInstall"&amp;gt;
+        &amp;lt;Exec WorkingDirectory="$(MpaRoot)" Command="npm run build"/&amp;gt;
+    &amp;lt;/Target&amp;gt;
+
+    &amp;lt;Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish"&amp;gt;
+        &amp;lt;!-- As part of publishing, ensure the JS resources are freshly built in production mode --&amp;gt;
+        &amp;lt;Exec WorkingDirectory="$(MpaRoot)" Command="npm install"/&amp;gt;
+        &amp;lt;Exec WorkingDirectory="$(MpaRoot)" Command="npm run build"/&amp;gt;
+
+        &amp;lt;!-- Include the newly-built files in the publish output --&amp;gt;
+        &amp;lt;ItemGroup&amp;gt;
+            &amp;lt;DistFiles Include="$(WWWRoot)dist\**"/&amp;gt;
+            &amp;lt;ResolvedFileToPublish Include="@(DistFiles-&amp;gt;'%(FullPath)')" Exclude="@(ResolvedFileToPublish)"&amp;gt;
+                &amp;lt;RelativePath&amp;gt;%(DistFiles.Identity)&amp;lt;/RelativePath&amp;gt;
+                &amp;lt;CopyToPublishDirectory&amp;gt;PreserveNewest&amp;lt;/CopyToPublishDirectory&amp;gt;
+                &amp;lt;ExcludeFromSingleFile&amp;gt;true&amp;lt;/ExcludeFromSingleFile&amp;gt;
+            &amp;lt;/ResolvedFileToPublish&amp;gt;
+        &amp;lt;/ItemGroup&amp;gt;
+    &amp;lt;/Target&amp;gt;
+
+    &amp;lt;Target Name="NpmClean" BeforeTargets="Clean"&amp;gt;
+        &amp;lt;RemoveDir Directories="$(WWWRoot)dist"/&amp;gt;
+        &amp;lt;RemoveDir Directories="$(MpaRoot)node_modules"/&amp;gt;
+    &amp;lt;/Target&amp;gt;
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; &amp;lt;/Project&amp;gt;
&lt;span class="err"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you may have figured out from the diff above, the &lt;code&gt;npm install&lt;/code&gt; command is &lt;em&gt;only&lt;/em&gt; executed in case the &lt;code&gt;node_modules&lt;/code&gt; directory is absent. That’s something to keep in mind in case you make modifications to &lt;code&gt;package.json&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;Now, we can build the project in its entirety using the &lt;code&gt;dotnet build&lt;/code&gt; command. Excellent! (pressing the run or compile button from your IDE works just as well)&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-building the bundle
&lt;/h3&gt;

&lt;p&gt;To make life even easier, we want to automagically rebuild the bundle whenever the front-end code changes. At the same time, we &lt;em&gt;don’t&lt;/em&gt; want to restart ASP.NET Core’s HTTP server (Kestrel) when that happens.&lt;/p&gt;

&lt;p&gt;To make this happen, we’ll add a webpack watcher for the front-end files to trigger a rebuild. In &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;

&lt;/span&gt; ...
     },
     "scripts": {
&lt;span class="gd"&gt;-        "build": "webpack"
&lt;/span&gt;&lt;span class="gi"&gt;+        "build": "webpack",
+        "watch": "webpack --watch"
&lt;/span&gt;     }
 }
&lt;span class="err"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;While editing front-end code, our workflow will look like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;npm run watch&lt;/code&gt; (executed from within the &lt;code&gt;ClientApp&lt;/code&gt; directory)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dotnet run&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;(Note: I would advise &lt;strong&gt;against&lt;/strong&gt; using &lt;code&gt;dotnet watch&lt;/code&gt; since it seems to continuously detect changes to the bundle, causing an endless rebuild loop)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gitlab.com/kdg-ti/integratieproject-1/guides/net8npmwebpack/-/tags/version-5" rel="noopener noreferrer"&gt;Version 5 of the sample project can be found here.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;We now have a flexible and extensible project that uses modern front-end technologies and has excellent performance.&lt;/p&gt;

&lt;p&gt;We’ve had to cover quite a bit of ground since many of these techniques are absent in most tutorials. Bower, Grunt, and Gulp were dominant just a few years ago but are now on their decline. Many sources on the internet still refer to these kings of yesteryear. However, on Bower’s website, you can see that they are actively recommending alternatives.&lt;/p&gt;

&lt;p&gt;This guide may have filled a gap by bringing npm and webpack into MVC and MPA applications, specifically .NET Core and .NET 8 apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s left?
&lt;/h3&gt;

&lt;p&gt;There is no distinction yet between “Development” and “Production”. Minification of JavaScript code and CSS pruning are still to be added. I’m confident, though, that the flexibility of the build system won’t make that too challenging.&lt;/p&gt;

&lt;p&gt;If you have any other suggestions, then please let me know in the comments below.&lt;/p&gt;

&lt;p&gt;Good luck building your MVC application!&lt;/p&gt;

</description>
      <category>aspnet</category>
      <category>mvc</category>
      <category>webpack</category>
      <category>npm</category>
    </item>
  </channel>
</rss>
