This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Friday, 28 November 2025
Als je hebt gebouwd traditionele ASP.NET Core MVC-toepassingen, je kent het probleem: dat gevreesd "klik op flash" wanneer gebruikers navigeren tussen pagina's. Volledige pagina herladen, de browser chroom flikkeren, inhoud springen rond als de nieuwe pagina rent. Het werkt, maar het doet het niet gevoel Modern.
De techniek van het terugsturen van gedeeltelijke HTML-fragmenten van de server en het ruilen ervan in de DOM lost dit op - en het is niet nieuw. Ik gebruik dit patroon al sinds de jQuery dagen, en zelfs daarvoor met vanille JavaScript en XMLHttpRequestWat veranderd is, is hoe... elegant Het is geworden met HTMX.
HTMX geeft ons een declaratieve manier om te doen wat we altijd hebben gedaan: HTML terugsturen naar de server en omruilen naar de pagina. Niet meer schrijven op maat JavaScript voor elke interactie. Niet meer kiezen tussen "juiste" server-side ontwikkeling en soepele gebruikerservaring. Met HTMX krijgen we beide.
Metgezel-artikel: Dit artikel richt zich op de ASP.NET Core integratie kant. Voor een diepe duik in HTMX gebeurtenissen, levenscyclus, en aangepaste extensies, zie mijn metgezel artikel: Een Whistle-stop Tour van HTMX Extensions en het gebruik van HTMX met ASP.NET Core.
In dit artikel zal ik je laten zien hoe HTMX mooi integreert met ASP.NET Core partials, hoe de uitstekende HTMX.NET bibliotheek maakt het nog beter, en hoe mijn meestal lucid.pagingtaghelper NuGet package maakt gebruik van HTMX biedt krachtige paginatie met minimale configuratie.
De aanpak is ook framework-agnost - Django voegde sjabloonfragmenten toe in versie 6.0, Rails heeft Turbo Frames, en het bredere web ecosysteem omarmt HTML-over-the-wire patronen. Het is een goed moment om server-rendered toepassingen te bouwen.
HTMX is een bibliotheek die u toegang geeft tot moderne browserfuncties direct vanuit HTML, in plaats van het schrijven van JavaScript. Het breidt HTML uit met attributen waarmee u AJAX-verzoeken kunt maken, content kunt uitwisselen en rijke interacties kunt creëren - allemaal zonder uw markup achter te laten.
De belangrijkste attributen die je het vaakst zult gebruiken:
hx-get, hx-post, hx-put, hx-delete - Maak HTTP-verzoekenhx-target - Geef aan waar het antwoord moet worden geplaatsthx-swap - Controleer hoe inhoud wordt geruild (innerHTML, outerHTML, enz.)hx-trigger - Definieer wat de activeert het verzoek (klik, verandering, laden, enz.)hx-push-url - Update de browser-URL zonder een volledige pagina herladenHier is de schoonheid van het: je bent nog steeds het schrijven van server-side code, het retourneren van server-rendred HTML. Geen JSON API's, geen client-side sjablonen, geen build pijpleidingen. Gewoon goede ouderwetse HTML over de draad.
Voordat HTMX, we bereikt hetzelfde effect met aanzienlijk meer ceremonie. Hier is hoe gedeeltelijke updates eruit zag in de jQuery tijdperk:
// jQuery circa 2010
$('#load-more').click(function() {
$.ajax({
url: '/posts/page/' + currentPage,
success: function(html) {
$('#posts-container').append(html);
currentPage++;
}
});
});
En nog eerder, met vanille JavaScript:
// Vanilla JS circa 2005
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
document.getElementById('content').innerHTML = xhr.responseText;
}
};
xhr.open('GET', '/partial-content', true);
xhr.send();
Het server-side patroon was identiek - HTML-fragmenten teruggeven, ze in de DOM ruilen. HTMX verplaatst deze logica van JavaScript naar HTML-attributen, waardoor het declarative, ontdekbaar en veel minder foutgevoelig is. De innovatie is niet de techniek; het is de interface.
ASP.NET ontwikkelaars doen dit al jaren. UpdatePanels in WebForms (2005), PartialView() in MVC sinds dag één, Html.RenderAction() Voor composible fragmenten - de mogelijkheid is er altijd geweest. Wat we ontbraken was een elegante, gestandaardiseerde manier om het te verbinden aan de client kant. HTMX vult dat gat perfect.
De bredere industrie heeft deze patronen ook herontdekt. Termen als "SSR" (Server-Side Rendering), "hybride rendering" en "eilanden architectuur" beschrijven in wezen wat server-side frameworks altijd hebben gedaan, maar met frisse ogen. Het is validatie dat de server-rendered approach schalen, uitvoeren, en - met de juiste tools - biedt uitstekende gebruikerservaring.
Neem eerst HTMX in uw lay-out op. U kunt een CDN gebruiken of lokaal serveren:
<script src="https://unpkg.com/[email protected]"></script>
Geen build step, geen npm install, geen webpack configuratie. Zet gewoon een script tag in en je gaat naar de races.
Voor reactiviteit aan de clientzijde (showing/hiding elements, toggling states, locale UI state), Alpine.jsunit synonyms for matching user input Complementeert het HTMX perfect. Bij slechts 15KB gzipped biedt het Vue/React-achtige declaratieve reactiviteit zonder de blaas.
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
Zo werken ze samen:
<div x-data="{ open: false }">
<button x-on:click="open = !open">Toggle</button>
<div x-show="open" x-transition>
<button hx-get="/api/data" hx-target="#results">Load Data</button>
</div>
</div>
Alpine behandelt lokale UI (de toggle), HTMX behandelt servergesprekken (de data fetch). Gedurende dit artikel, zie je dit patroon - Alpine voor client reactiviteit, HTMX voor server interacties.
Khalid Abuhakmeh's HTMX.NET bibliotheek biedt eersteklas ASP.NET Core integratie. Verkrijgbaar als Htmx en Htmx.TagHelpers NuGet pakketten, het voelt inheems aan .NET en maakt het werken met HTMX een absolute plezier. U vindt meer van Khalid's uitstekende open-source werk op zijn GitHub.
dotnet add package Htmx
dotnet add package Htmx.TagHelpers
In uw _ViewImports.cshtml:
@addTagHelper *, Htmx.TagHelpers
De meest nuttige functie is de Request.IsHtmx() extensie methode, die u vertelt of het verzoek kwam van HTMX. Hiermee kunt u ofwel een volledige weergave of slechts een gedeeltelijke:
[HttpGet]
public async Task<IActionResult> Index(int page = 1, int pageSize = 20)
{
var posts = await blogViewService.GetPagedPosts(page, pageSize);
if (Request.IsHtmx())
return PartialView("_BlogSummaryList", posts);
return View("Index", posts);
}
Dit patroon is absoluut briljant. Een enkele controller actie dient beide:
Geen aparte API eindpunten, geen gedupliceerde logica, geen JSON serialisatie overhead.
Een veelvoorkomende misvatting: Veel ASP.NET ontwikkelaars denken dat je de
_voorvoegsel (zoals_BlogSummaryList.cshtml) om gedeeltelijke weergave te krijgen.PartialView()methode zelf vertelt ASP.NET Core om de lay-out over te slaan - het zegt "vergeet de lay-out, gewoon render dit bit." U kunt gebruikenreturn PartialView("SearchResults", model)met een regelmatig weergavebestand en het werkt perfect. De underscore conventie is afkomstig van ASP.NET Web Pages (WebMatrix) waar het voorkomen dat bestanden direct via URL worden bediend - maar MVC heeft altijd beschermd alle views van directe toegang toch. Het is puur een naamgeving conventie om te helpen bij het identificeren van views bestemd als deelstukken.
HTMX.NET biedt tag helpers die het werken met controllers schoner maken. In plaats van route strings te schrijven, kunt u sterk getypte referenties gebruiken:
<button
hx-controller="Comment"
hx-action="GetCommentForm"
hx-post
hx-target="#commentform">
Reply
</button>
Dit genereert de juiste route met behulp van ASP.NET Core's routing systeem. Als u uw controller of actie hernoemt, zal uw IDE het vangen. Veel beter dan magische strings!
Hier is een echt voorbeeld van deze blog commentaar systeem:
<button
class="btn btn-outline btn-sm mb-4"
hx-action="Comment"
hx-controller="Comment"
hx-post
hx-vals
x-on:click.prevent="window.mostlylucid.comments.setValues($event)"
hx-on="htmx:afterSwap: window.scrollTo({top: 0, behavior: 'smooth'})"
hx-swap="outerHTML"
hx-target="#commentform">
Comment
</button>
Merk op hoe HTMX mooi speelt met Alpine.js (x-on:click.prevent) voor die occasionele stukjes interactiviteit aan de cliëntzijde.
De bibliotheek biedt ook:
Request.IsHtmxNonBoosted() - Controleer of het een HTMX verzoek is maar niet versterktRequest.IsHtmxRefresh() - Controleer of het een herstelverzoek voor geschiedenis isHier is de zoekcontroller van deze blog, die het drie-tier retour patroon toont:
[HttpGet]
[OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request", "pagerequest" })]
public async Task<IActionResult> Search(
string? query,
int page = 1,
int pageSize = 10,
[FromHeader] bool pagerequest = false)
{
var searchModel = await BuildSearchModel(query, page, pageSize);
if (pagerequest && Request.IsHtmx())
return PartialView("_SearchResultsPartial", searchModel.SearchResults);
if (Request.IsHtmx())
return PartialView("SearchResults", searchModel);
return View("SearchResults", searchModel);
}
Drie terugkeerpaden voor drie scenario's:
De gedeeltelijke weergave (_SearchResultsPartial.cshtml) gebruikt de paging tag helper:
@model Mostlylucid.Models.Blog.PostListViewModel
<div class="pt-2" id="content">
@if (Model.Data?.Any() is true)
{
<div class="inline-flex w-full items-center justify-center pb-4">
@if (Model.TotalItems > Model.PageSize)
{
<pager
x-ref="pager"
link-url="@Model.LinkUrl"
hx-boost="true"
hx-target="#content"
hx-swap="show:none"
page="@Model.Page"
page-size="@Model.PageSize"
total-items="@Model.TotalItems"
hx-headers='{"pagerequest": "true"}'>
</pager>
}
</div>
@foreach (var post in Model.Data)
{
<partial name="_ListPost" model="post"/>
}
}
</div>
Het afbreken van de pager tag helper:
hx-boost="true" - Intercepteert koppelingen, converteert naar AJAXhx-target="#content" - Waar moet u de reactie injecteren?hx-headers='{"pagerequest": "true"}' - Aangepaste header vertelt de controller dat het paginatie isRequest.IsHtmx() && pagerequest om slechts het minimale gedeeltelijke terug te gevenIk schreef meestal lucid.pagingtaghelper om repetitieve paginatie code te vermijden. Het is HTMX-eerste maar werkt ook zonder JavaScript.
dotnet add package mostlylucid.pagingtaghelper
Toevoegen aan _ViewImports.cshtml:
@addTagHelper *, mostlylucid.pagingtaghelper
Uitvoeren IPagingModel<T> En je bent klaar:
public class BasePagingModel<T> : IPagingModel<T> where T : class
{
public int Page { get; set; }
public int TotalItems { get; set; }
public int PageSize { get; set; }
public string LinkUrl { get; set; }
public List<T> Data { get; set; }
}
Wat je krijgt:
De tag helper genereert links die query strings behouden, aangepaste headers ondersteunen en naadloos integreren met HTMX (zie het voorbeeld hierboven).
Zo past het hele systeem in elkaar:
graph TB
A[Browser] -->|"Initial page load"| B[Controller Action]
B -->|"Request.IsHtmx() = false"| C[Return Full View]
C --> D[Render Layout + Partial]
A -->|"User clicks pagination/filter"| E[HTMX Request]
E -->|"hx-get with headers"| F[Same Controller Action]
F -->|"Request.IsHtmx() = true"| G{Check Headers}
G -->|"pagerequest header"| H[Return Minimal Partial]
G -->|"No special header"| I[Return Section Partial]
H --> J[Swap Content in Target]
I --> J
J -->|"User clicks another link"| E
style B stroke:#333,stroke-width:2px
style F stroke:#333,stroke-width:2px
style C stroke:#0066cc,stroke-width:2px
style H stroke:#00cc66,stroke-width:2px
style I stroke:#00cc66,stroke-width:2px
Django toegevoegd goede gedeeltelijke sjabloon rendering in versie 6.0 (december 2024) met sjabloonfragmenten. Daarvoor, Django ontwikkelaars meestal gebruikt inclusie-tags of pakketten van derden zoals django-render-block. ASP.NET Core heeft gehad PartialView() sinds versie 1.0 in 2016 - verschillende kaders, verschillende tijdlijnen, maar dezelfde bestemming: HTML fragmenten voor HTMX.
Ruby on Rails heeft Turbo Frames (onderdeel van Hotwire), wat qua spirit vergelijkbaar is:
<%= turbo_frame_tag "posts" do %>
<%= render @posts %>
<% end %>
Het verschil is dat Turbo specifieke frame markers nodig heeft op zowel verzoek als reactie. HTMX is flexibeler - elk eindpunt kan elke HTML teruggeven, en u bepaalt waar het gaat met hx-target.
Elixir's Phoenix LiveView kiest een andere aanpak met aanhoudende WebSocket verbindingen en server-side status:
def handle_event("load_more", _params, socket) do
{:noreply, assign(socket, posts: load_more_posts())}
end
LiveView is briljant voor real-time toepassingen, maar het vereist WebSocket infrastructuur en servergeheugen voor verbindingen. HTMX maakt gebruik van gewone oude HTTP - stateloos, cacheable, schaalbaar. Voor een blog, dat is perfect.
Output Caching: De OutputCache attribuut varieert met hx-request kop, caching volledige pagina's en delen afzonderlijk:
[OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request", "pagerequest" })]
Netwerkefficiëntie: Server-rendered HTML is vaak kleiner dan JSON + client-side sjablonen, vereist minder ronde reizen, en caches goed.
Bundelgrootte: HTMX (14KB) + optioneel Alpine.js (15KB) + paging tag helper (0KB, server-side) = onder 30KB totaal. Vergelijk dat met een typische React app (200KB+).
Optimistische UI-updates - Combineer HTMX en Alpine voor directe feedback:
<div x-data="{ count: @Model.CommentCount }">
<button hx-post="/comment/like" x-on:click="count++" hx-on::after-request="count = $event.detail.xhr.response">
Likes: <span x-text="count"></span>
</button>
</div>
De count updates onmiddellijk (optimalistisch), dan synchroniseert met de server response.
Uit-Band SwapStencils - Update meerdere pagina secties uit één antwoord:
<div id="main-content"><!-- Main response --></div>
<div id="notification-count" hx-swap-oob="true"><span>5 new</span></div>
Perfect voor notificatie badges, kar telt, enz. Ik bedek dit patroon in diepte in Toast en Swapping tonen met HTMX.
Opdrachtgever-zijsjablonen met WebAPI - Soms wil je een mid-way ervaring: server-rendered HTML voor de meeste dingen, maar JSON van een WebAPI voor specifieke dynamische inhoud. HTMX's extensie client-side-templates laat je precies dit doen:
<script src="https://unpkg.com/[email protected]/client-side-templates.js"></script>
<script src="https://unpkg.com/mustache@latest"></script>
<div hx-ext="client-side-templates">
<template id="post-template" type="text/mustache">
{{#posts}}
<div class="post">
<h3>{{title}}</h3>
<p>{{excerpt}}</p>
</div>
{{/posts}}
</template>
<button hx-get="/api/posts"
hx-target="#post-list"
mustache-template="post-template">
Load Posts
</button>
<div id="post-list"></div>
</div>
Deze aanpak werkt met sjablonen van Snor, Handlebars of Nunjucks. Uw WebAPI geeft JSON terug, maar HTMX behandelt de rendering client-side. Het is vooral handig wanneer u een bestaande API hebt of gegevens wilt delen met mobiele apps. Voor meer details over HTMX-extensies inclusief client-side-templates, zie het begeleidende artikel.
Hulpmiddel voor debuggen: Installeer de HTMX debugger-extensie - het toont elk verzoek, antwoord en ruil in real-time.
ASP.NET Core's antiforgery tokens hebben speciale behandeling nodig met AJAX. HTMX.NET biedt verschillende schone opties. De aanbevolen manier van gebruik HtmxAntiforgeryScriptEndpoint:
// In Program.cs
app.MapHtmxAntiforgeryScript();
<!-- In your layout head -->
<script src="@HtmxAntiforgeryScriptEndpoints.Path" defer></script>
Of gebruik de tag helper: <meta name="htmx-config" includeAspNetAntiforgeryToken="true" />. Zie Khalid's artikel over HTMX Anti-Forgery Tokens voor alle details.
Alpine's @click stenoconflicten met Razor's @ syntax. Gebruik in plaats daarvan het expliciete formulier:
<button x-on:click="doSomething()">Click me</button> <!-- Instead of @click -->
<div x-bind:class="isOpen ? 'block' : 'hidden'"></div> <!-- Instead of :class -->
Of ontsnappen met @@click, maar expliciete syntaxis is duidelijker.
Geschiedenis-items controleren: Standaard duwt HTMX elke aanvraag naar de geschiedenis. Voor paginatie/filters waar u geen vervuiling van de geschiedenis wilt:
<paging model="@Model" hx-push-url="false"> <!-- Don't add history entries -->
Of gebruik hx-replace-url="true" om de URL bij te werken zonder inzendingen toe te voegen.
Het Phantom Partiële probleem: Browser back/forward toont slechts een gedeeltelijk fragment zonder lay-out. hx-history-elt op uw inhoudscontainer:
<div class="container mx-auto" id="contentcontainer" hx-history-elt>
@RenderBody()
</div>
Dit vertelt HTMX welk element snapshot, het behoud van de omliggende lay-out bij het herstellen van de geschiedenis.
Symptoom: HTMX werkt lokaal maar breekt achter een CDN - verkeerde inhoud wordt gecached.
Hoofdoorzaak: CDN's negeren de HX-Request header. Uw server geeft verschillende inhoud terug op basis van deze header, maar de CDN caches ze identiek.
ASP.NET Core fix: Gebruik VaryByHeaderNames:
[OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request" })]
CDN-fix: Cache-regels configureren om op te nemen HX-Request in de cache sleutel. Voor Cloudflare: Dashboard → Caching → Cache Rules → Aangepaste cache sleutel → Headers → Include HX-Request.
hx-boost zet normale links om in AJAX-verzoeken. HTMX-quirks-pagina merkt op dat sommige kernteamleden aanbevelen het te vermijden (verwerpt <head> inhoud, beïnvloedt de plaats van het gedrag), terwijl anderen vinden het prima voor snelle overwinningen.
<div hx-boost="true" hx-target="#contentcontainer">
<a asp-action="Show" asp-route-slug="@post.Slug">Read More</a>
</div>
Deze blog gebruikt het uitgebreid. Bij het richten van specifieke containers, expliciet hx-get is duidelijker, maar hx-boost werkt als je consistent bent. Uitschakelen selectief met hx-boost="false" over kinder-elementen.
HTMX met ASP.NET Core partials vertegenwoordigt een terugkeer naar server-side eenvoud zonder opoffering van moderne UX. U krijgt:
De server-rendered aanpak heeft de test van de tijd doorstaan, en met HTMX, heeft het eindelijk de elegante client-side tooling die het verdient. U kunt robuuste, performante webapplicaties bouwen met behulp van patronen die al decennia werken - ze hebben gewoon gewacht op de juiste tool om ze weer te laten schijnen.
Dit artikel maakt deel uit van een tweedelige serie op HTMX met ASP.NET Core:
Officiële documentatie:
Bibliotheken & hulpprogramma's:
Communautaire middelen:
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.