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
Om du har byggt traditionella ASP.NET Core MVC program, du vet problemet: som fruktade "klicka blixt" när användare navigera mellan sidor. Helsida laddar om, webbläsaren krom flimmer, innehåll hoppa runt som den nya sidan renders. Det fungerar, men det gör det inte känsla Modernt.
Tekniken att returnera partiella HTML-fragment från servern och byta dem till DOM löser detta - och det är inte nytt. Jag har använt detta mönster sedan jQuery dagar, och även innan det med vanilj JavaScript och XMLHttpRequestVad som har förändrats är hur elegant Det har blivit med HTMX.
HTMX ger oss ett deklarativt sätt att göra det vi alltid har gjort: returnera serverrenderad HTML och byt ut den till sidan. Inget mer skrivande anpassat JavaScript för varje interaktion. Inget mer val mellan "riktig" server-sida utveckling och smidig användarupplevelse. Med HTMX får vi båda.
Tilläggsartikel: Denna artikel fokuserar på ASP.NET Core integration sida. För en djupdykning i HTMX händelser, livscykel, och anpassade förlängningar, se min följeslagare artikel: En Whistle-stop Tour av HTMX-tillägg och användning av HTMX med ASP.NET Core.
I den här artikeln ska jag visa dig hur HTMX integreras vackert med ASP.NET Core partishs, hur den utmärkta HTMX.NET Ordförande biblioteket gör det ännu bättre, och hur min mestadelslucid.pagingtaghelper NuGet paket använder HTMX ger kraftfull paginering med minimal konfiguration.
Tillvägagångssättet är ram-agnostiskt också - Django lagt mallfragment i version 6.0 Rails har Turbo Frames, och det bredare webbekosystemet omfamnar HTML-over-the-wire mönster. Det är en bra tid att bygga server-renderade program.
HTMX är ett bibliotek som låter dig komma åt moderna webbläsarfunktioner direkt från HTML, snarare än att skriva JavaScript. Det utökar HTML med attribut som gör att du kan göra AJAX-förfrågningar, byta innehåll och skapa rika interaktioner - allt utan att lämna din markering.
De viktigaste attributen du använder oftast:
hx-get, hx-post, hx-put, hx-delete - Gör HTTP-förfrågningarhx-target - Ange var svaret ska ställashx-swap - Kontrollera hur innehåll byts (innerHTML, yttreHTML, etc.)hx-trigger - Definiera vad som utlöser begäran (klicka, ändra, ladda, etc.)hx-push-url - Uppdatera webbläsarens URL utan en hel sida ladda omHär är skönheten i det: du skriver fortfarande server-side-kod, returnera server-renderade HTML. Inga JSON API:er, inga klient-side mallar, inga byggpipelines. Bara god gammaldags HTML över tråden.
Före HTMX uppnådde vi samma effekt med betydligt mer ceremoni. Så här såg partiella uppdateringar ut under jQuery-eran:
// jQuery circa 2010
$('#load-more').click(function() {
$.ajax({
url: '/posts/page/' + currentPage,
success: function(html) {
$('#posts-container').append(html);
currentPage++;
}
});
});
Och ännu tidigare, med vanilj 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();
Serversidan mönstret var identiskt - returnera HTML-fragment, byta dem till DOM. HTMX flyttar bara denna logik från JavaScript till HTML-attribut, vilket gör det deklarativt, upptäckbart och mycket mindre felbenäget. Innovationen är inte tekniken; det är gränssnittet.
ASP.NET utvecklare har gjort detta i åratal. UpdatePanels i WebForms (2005), PartialView() i MVC sedan första dagen, Html.RenderAction() För komposerbara fragment - kapaciteten har alltid funnits där. Vad vi saknade var ett elegant, standardiserat sätt att koppla upp det på klientsidan. HTMX fyller den luckan perfekt.
Den bredare industrin har återupptäckt dessa mönster också. Termer som "SSR" (Server-Side Rendering), "hybrid rendering", och "öar arkitektur" beskriver i huvudsak vad server-side ramar har alltid gjort, men med färska ögon. Det är validering att server-renderade tillvägagångssätt skalor, utför, och - med rätt verktyg - ger utmärkt användarupplevelse.
Först, inkludera HTMX i din layout. Du kan använda en CDN eller servera den lokalt:
<script src="https://unpkg.com/[email protected]"></script>
Inget byggsteg, ingen installation, inget webbpaket.
För återaktivitet på klientsidan (visar/gömmer element, sammanslagna stater, lokal användarstat). Alpina.js kompletterar HTMX perfekt. På bara 15KB gzippad, ger det Vue / React-liknande deklarativ reaktivitet utan uppblåst.
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
Så här arbetar de tillsammans:
<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 hanterar lokala användargränssnitt (växla), HTMX hanterar serversamtal (data hämta). Under hela denna artikel, du kommer att se detta mönster - Alpine för klientreaktivitet, HTMX för serverinteraktioner.
Khalid Abuhakmeh Ordförandeär HTMX.NET Ordförande biblioteket tillhandahåller förstklassig ASP.NET Core integration. Tillgänglig som Htmx och Htmx.TagHelpers NuGet paket, det känns naturligt att .NET och gör arbetet med HTMX till ett absolut nöje. Du hittar mer av Khalid utmärkta open-source arbete på hans GitHub Ordförande.
dotnet add package Htmx
dotnet add package Htmx.TagHelpers
I din _ViewImports.cshtml:
@addTagHelper *, Htmx.TagHelpers
Den mest användbara funktionen är Request.IsHtmx() förlängningsmetod, som talar om för dig om begäran kom från HTMX. Detta låter dig returnera antingen en fullständig vy eller bara en partiell:
[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);
}
Detta mönster är helt lysande. En enda styrenhet åtgärder tjänar båda:
Inga separata API-slutpunkter, ingen duplicerad logik, inga JSON-serier.
En vanlig missuppfattning: Många ASP.NET utvecklare tror att du behöver
_prefix (som_BlogSummaryList.cshtml) för att få partiell rendering. Det gör du inte!PartialView()metoden själv berättar ASP.NET Core att hoppa över layouten - det säger "glöm layouten, bara rendera denna bit". Du kan användareturn PartialView("SearchResults", model)med en vanlig vy fil och det fungerar perfekt. Underscore konventionen har sitt ursprung från ASP.NET webbsidor (WebMatrix) där det förhindrade filer från att serveras direkt via URL - men MVC har alltid skyddat alla vyer från direkt tillgång ändå. Det är bara en namngivning konvention för att hjälpa till att identifiera vyer avsedd som partiella.
HTMX.NET tillhandahåller tagghjälpare som gör det renare att arbeta med regulatorer. Istället för att skriva ruttsträngar kan du använda starkt typade referenser:
<button
hx-controller="Comment"
hx-action="GetCommentForm"
hx-post
hx-target="#commentform">
Reply
</button>
Detta genererar rätt rutt med hjälp av ASP.NET Core routing system. Om du byter namn på din styrenhet eller åtgärd, din IDE kommer att fånga det. Mycket bättre än magiska strängar!
Här är ett riktigt exempel från den här bloggens kommentarsystem:
<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>
Lägg märke till hur HTMX spelar fint med Alpine.js (x-on:click.prevent) för de enstaka bitar av klient-sidan interaktivitet.
I biblioteket finns även följande:
Request.IsHtmxNonBoosted() - Kontrollera om det är en HTMX begäran men inte förstärktRequest.IsHtmxRefresh() - Kolla om det är en historik återställa begäranHär är sökkontrollen från den här bloggen, som visar tre-stegs returmönster:
[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);
}
Tre returvägar för tre scenarier:
Delvyn (_SearchResultsPartial.cshtml) använder personsökningsetikettens hjälpare:
@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>
Bryt ner personsökartaggens hjälpare:
hx-boost="true" - Intercepterar länkar, konverterar till AJAXhx-target="#content" - Var ska svaret injiceras?hx-headers='{"pagerequest": "true"}' - Skräddarsydd rubrik säger till styrenheten att det är sidnumrering.Request.IsHtmx() && pagerequest för att returnera bara den minsta partiellaJag skrev mestadelslucid.pagingtaghelper för att undvika upprepad paginering kod. Det är HTMX- först men fungerar utan JavaScript också.
dotnet add package mostlylucid.pagingtaghelper
Lägg till i _ViewImports.cshtml:
@addTagHelper *, mostlylucid.pagingtaghelper
Genomförande IPagingModel<T> och du är klar:
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; }
}
Vad du får:
Tag-hjälparen genererar länkar som bevarar frågesträngar, stöder anpassade rubriker och integrerar sömlöst med HTMX (se exemplet ovan).
Så här passar hela systemet ihop:
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 lade till korrekt partiell mallredogörelse i version 6.0 (december 2024) med mallfragment. Innan dess använde Djangoutvecklare vanligtvis integrationstaggar eller tredjepartspaket som django-render-block. ASP.NET Core har haft PartialView() Sedan version 1.0 2016 - olika ramverk, olika tidslinjer, men samma destination: HTML-fragment för HTMX.
Ruby on Rails har Turbo Frames (del av Hotwire), som är liknande i anden:
<%= turbo_frame_tag "posts" do %>
<%= render @posts %>
<% end %>
Skillnaden är att Turbo kräver specifika rammarkörer på både begäran och svar. HTMX är mer flexibel - alla endpoint kan returnera någon HTML, och du bestämmer var det går med hx-target.
Elixir's Phoenix LiveView tar en annan strategi med ihållande WebSocket anslutningar och server-sidan tillstånd:
def handle_event("load_more", _params, socket) do
{:noreply, assign(socket, posts: load_more_posts())}
end
LiveView är lysande för realtidsapplikationer, men det kräver WebSocket infrastruktur och serverminne för anslutningar. HTMX använder vanlig gammal HTTP - statslös, cachebar, skalbar. För en blogg, det är perfekt.
Utmatningscache: för OutputCache attributet varierar med hx-request header, caching hela sidor och partiella separat:
[OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request", "pagerequest" })]
Nätverkseffektivitet: Server-renderad HTML är ofta mindre än JSON + klient-side mallar, kräver färre turer, och cache ordentligt.
Bundelstorlek: HTMX (14KB) + tillval Alpine.js (15KB) + personsökning tagghjälp (0KB, server-sida) = under 30KB totalt. Jämför det med en typisk React app (200KB+).
Optimistiska uppdateringar av användargränssnittet - Kombinera HTMX och Alpine för omedelbar 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>
Räkna uppdateringar omedelbart (optimistisk), sedan synkroniseras med serverns svar.
Utombords Swaps - Uppdatera flera sidavsnitt från ett svar:
<div id="main-content"><!-- Main response --></div>
<div id="notification-count" hx-swap-oob="true"><span>5 new</span></div>
Perfekt för anmälningsbrickor, vagn räknas, etc. Jag täcker detta mönster på djupet i Visar toast och växling med HTMX.
Mallar för klientsidor med WebAPI - Ibland vill du ha en halvvägs upplevelse: server-renderad HTML för de flesta saker, men JSON från en WebAPI för specifikt dynamiskt innehåll. HTMX's förlängning av klient-side-mallar Låter dig göra exakt detta:
<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>
Detta tillvägagångssätt fungerar med Mustache, Handtag eller Nunjucks mallar. Din WebAPI returnerar JSON, men HTMX hanterar renderingsklientens sida. Det är särskilt användbart när du har ett befintligt API eller behöver dela data med mobila appar. För mer information om HTMX-tillägg, inklusive klient-side-mallar, se sällskapsartikeln.
Felsökningsverktyg: Installera HTMX- felsökningsförlängning - det visar varje begäran, svar, och byta i realtid.
ASP.NET Core antiforgery polletter behöver särskild hantering med AJAX. HTMX.NET ger flera rena alternativ. Det rekommenderade sättet användning HtmxAntiforgeryScriptEndpoint:
// In Program.cs
app.MapHtmxAntiforgeryScript();
<!-- In your layout head -->
<script src="@HtmxAntiforgeryScriptEndpoints.Path" defer></script>
Eller använd tagghjälp: <meta name="htmx-config" includeAspNetAntiforgeryToken="true" />. Se också Khalids artikel om HTMX Anti-Forgery Tokens För alla detaljer.
Alperna @click korta konflikter med Razor's @ syntax. Använd det explicita formuläret istället:
<button x-on:click="doSomething()">Click me</button> <!-- Instead of @click -->
<div x-bind:class="isOpen ? 'block' : 'hidden'"></div> <!-- Instead of :class -->
Eller fly med @@click, men explicit syntax är tydligare.
Kontroll av historikposter: Normalt skjuter HTMX varje begäran till historik. För paginering/filter där du inte vill ha historikföroreningar:
<paging model="@Model" hx-push-url="false"> <!-- Don't add history entries -->
Eller använd hx-replace-url="true" uppdatera webbadressen utan att lägga till poster.
Phantoms delproblem: Webbläsaren tillbaka/framåt visar bara ett partiellt fragment utan layout. Fixa med hx-history-elt På din innehållsbehållare:
<div class="container mx-auto" id="contentcontainer" hx-history-elt>
@RenderBody()
</div>
Detta talar om för HTMX vilket element för att ögonblicksbilda, bevara den omgivande layouten när du återställer historiken.
Symptom: HTMX fungerar lokalt men bryter bakom en CDN - fel innehåll blir cachad.
Rotorsak: CDNs ignorerar HX-Request header. Servern returnerar olika innehåll baserat på detta header, men CDN cache dem identiskt.
ASP.NET Core fix: Användning VaryByHeaderNames:
[OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request" })]
CDN-fix: Anpassa cacheregler för att inkludera HX-Request För Cloudflare: Dashboard → Caching → Cache Regler → Anpassad cache nyckel → Rubriker → Inkludera HX-Request.
hx-boost konverterar normala länkar till AJAX-förfrågningar. HTMX egenheter sida noterar några kärnteam medlemmar rekommenderar att undvika det (utfärdar <head> innehåll, påverkar platsen för beteendet), medan andra tycker att det är bra för snabba vinster.
<div hx-boost="true" hx-target="#contentcontainer">
<a asp-action="Show" asp-route-slug="@post.Slug">Read More</a>
</div>
Den här bloggen använder den i stor utsträckning. När man riktar in sig på specifika behållare, explicit hx-get är tydligare, men hx-boost Fungerar om du är konsekvent. Inaktivera selektivt med hx-boost="false" för barnelement.
HTMX med ASP.NET Core partiella representerar en återgång till server-side enkelhet utan att offra moderna UX. Du får:
Den server-renderade metoden har stått sig med tiden, och med HTMX har den äntligen den eleganta klient-side verktyg den förtjänar. Du kan bygga robusta, performant webbapplikationer med mönster som har fungerat i årtionden - de har bara väntat på rätt verktyg för att få dem att lysa igen.
Denna artikel är en del av en tvådelad serie på HTMX med ASP.NET Core:
Officiell dokumentation:
Bibliotek och verktyg:
Gemenskapens resurser:
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.