Se hai costruito le tradizionali applicazioni ASP.NET Core MVC , si conosce il problema: che temuto " fare clic su flash " quando gli utenti navigano tra le pagine . Ricarica pagina completa , il browser cromata flickering , contenuti saltando in giro come la nuova pagina rende . Funziona , ma non ensazione di Moderno.
La tecnica di restituire frammenti HTML parziali dal server e di scambiarli nel DOM risolve questo - e non è nuova. Ho usato questo modello fin dai giorni jQuery, e anche prima con vaniglia JavaScript e XMLHttpRequest. Quello che è cambiato è come elegante E' diventato con l'HTMX.
HTMX ci offre un modo dichiarativo per fare quello che abbiamo sempre fatto: restituire HTML fornito dal server e scambiarlo nella pagina. Niente più scrittura JavaScript personalizzato per ogni interazione. Niente più scelta tra lo sviluppo "proprio" lato server e l'esperienza utente fluida. Con HTMX, otteniamo entrambi.
Articolo Companion: Questo articolo si concentra sul lato ASP.NET Core integrazione. Per una profonda immersione in eventi HTMX, ciclo di vita, e estensioni personalizzate, vedere il mio articolo compagno: A Whistle-stop Tour di estensioni HTMX e l'utilizzo di HTMX con ASP.NET Core.
In questo articolo, vi mostrerò come HTMX si integra splendidamente con ASP.NET Core parziale, come il eccellente HTMX.NET biblioteca rende ancora meglio, e come il mio per lo piùlucid.pagingtaghelper Il pacchetto NuGet utilizza HTMX fornisce una potente paginazione con una configurazione minima.
L'approccio è framework-agnostico troppo - Django ha aggiunto frammenti di template nella versione 6.0, Rails ha Turbo Frames, e l'ecosistema web più ampio sta abbracciando HTML-over-the-wire pattern. E 'un buon momento per essere la costruzione di applicazioni dotate di server.
HTMX è una libreria che consente di accedere alle moderne funzionalità del browser direttamente da HTML, piuttosto che scrivere JavaScript. Si estende HTML con attributi che consentono di effettuare richieste AJAX, scambiare contenuti e creare interazioni ricche - il tutto senza lasciare il tuo markup.
Gli attributi chiave che userai più spesso:
hx-get, hx-post, hx-put, hx-delete - Effettuare richieste HTTPhx-target - Specificare dove mettere la rispostahx-swap - Controllare come il contenuto viene scambiato (HTML interno, HTML esterno, ecc.)hx-trigger - Definire cosa innesca la richiesta (click, cambio, carico, ecc.)hx-push-url - Aggiornare l'URL del browser senza ricaricare l'intera paginaEcco la bellezza: stai ancora scrivendo codice server-side, restituendo server-rendered HTML. Nessuna API JSON, nessun modello client-side, nessun build pipeline. Solo un buon vecchio stile HTML sopra il filo.
Prima di HTMX, abbiamo ottenuto lo stesso effetto con molta più cerimonia. Ecco come sono stati gli aggiornamenti parziali nell'era jQuery:
// jQuery circa 2010
$('#load-more').click(function() {
$.ajax({
url: '/posts/page/' + currentPage,
success: function(html) {
$('#posts-container').append(html);
currentPage++;
}
});
});
E anche prima, con vaniglia 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();
Il modello lato server era identico - restituire frammenti HTML, scambiarli nel DOM. HTMX sposta solo questa logica da JavaScript in attributi HTML, rendendolo dichiarativo, scovabile, e molto meno error-prone. L'innovazione non è la tecnica; è l'interfaccia.
Gli sviluppatori di ASP.NET lo fanno da anni. AggiornarePanel in WebForms (2005), PartialView() in MVC dal primo giorno, Html.RenderAction() per i frammenti composabili - la capacità è sempre stata presente. Ciò che ci mancava era un modo elegante e standardizzato per collegarlo sul lato client. HTMX riempie perfettamente questo spazio.
Anche l'industria più ampia sta riscoprendo questi modelli. Termini come "SSR" (Server-Side Rendering), "hybrid rendering" e "ilands architecture" stanno essenzialmente descrivendo ciò che i framework sul lato server hanno sempre fatto, ma con occhi nuovi. È la validazione che le scale di approccio rese dal server, le prestazioni e - con gli strumenti giusti - forniscono un'eccellente esperienza utente.
In primo luogo, includere HTMX nel vostro layout. È possibile utilizzare una CDN o servirla localmente:
<script src="https://unpkg.com/[email protected]"></script>
Niente build step, niente installazione di npm, niente configurazione di webpack.
Per la reattività lato client (mostrare/nascondere elementi, stati di commutazione, stato UI locale), Alpine.js completa perfettamente HTMX. A soli 15KB gzipped, fornisce Vue/React-like reattività dichiarativa senza il bloat.
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
Ecco come lavorano insieme:
<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 gestisce l'interfaccia utente locale (il toggle), HTMX gestisce le chiamate del server (il recupero dati). In questo articolo, vedrete questo modello - Alpine per la reattività del client, HTMX per le interazioni del server.
Khalid Abuhakmeh's HTMX.NET libreria fornisce l'integrazione core ASP.NET di prima classe. Htmx e Htmx.TagHelpers NuGet pacchetti, si sente nativo di .NET e rende lavorare con HTMX un piacere assoluto. Troverete più di Khalid eccellente lavoro open-source sul suo GitHubCity name (optional, probably does not need a translation).
dotnet add package Htmx
dotnet add package Htmx.TagHelpers
Nella tua _ViewImports.cshtml:
@addTagHelper *, Htmx.TagHelpers
La caratteristica più utile è la Request.IsHtmx() metodo di estensione, che ti dice se la richiesta è arrivata da HTMX. Questo consente di restituire una vista completa o solo una parziale:
[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);
}
Questo modello è assolutamente brillante. Una singola azione del controller serve entrambi:
Nessun endpoint API separato, nessuna logica duplicata, nessuna serializzazione JSON.
Un errore comune: Molti sviluppatori ASP.NET pensano che è necessario il
_prefisso (come_BlogSummaryList.cshtml) per ottenere il rendering parziale.PartialView()metodo stesso dice ASP.NET Core di saltare il layout - sta dicendo "dimentica il layout, basta renderizzare questo bit." È possibile utilizzarereturn PartialView("SearchResults", model)con un file di visualizzazione regolare e funziona perfettamente. La convenzione di sottolineatura è nata da pagine web ASP.NET (WebMatrix) dove ha impedito che i file fossero serviti direttamente tramite URL - ma MVC ha sempre protetto tutte le viste dall'accesso diretto comunque. E 'solo una convenzione di denominazione per aiutare a identificare le viste destinati come parziali.
HTMX.NET fornisce aiutanti di tag che rendono lavorare con i controller più puliti. Invece di scrivere stringhe di route, è possibile utilizzare riferimenti fortemente digitati:
<button
hx-controller="Comment"
hx-action="GetCommentForm"
hx-post
hx-target="#commentform">
Reply
</button>
Questo genera il percorso corretto usando il sistema di routing di ASP.NET Core. Se rinomini il controller o l'azione, il tuo IDE lo catturerà. Molto meglio delle stringhe magiche!
Ecco un vero esempio dal sistema di commento di questo blog:
<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>
Notare come HTMX gioca bene con Alpine.js (x-on:click.prevent) per quei bit occasionali di interattività client-side.
La biblioteca fornisce inoltre:
Request.IsHtmxNonBoosted() - Controllare se si tratta di una richiesta HTMX ma non potenziatoRequest.IsHtmxRefresh() - Controlla se è una richiesta di ripristino della cronologiaEcco il controllore di ricerca da questo blog, mostrando il modello di ritorno a tre livelli:
[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 percorsi di ritorno per tre scenari:
La vista parziale (_SearchResultsPartial.cshtml) utilizza l'helper del tag paging:
@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>
Spezzare l'helper del tag del pager:
hx-boost="true" - Intercetta collegamenti, converte in AJAXhx-target="#content" - Dove iniettare la rispostahx-headers='{"pagerequest": "true"}' - Intestazione personalizzata dice al controller che è paginazioneRequest.IsHtmx() && pagerequest per restituire solo il minimo parzialeHo scritto per lo piùlucid.pagingtaghelper per evitare il codice di paginazione ripetitivo. E 'HTMX-primo ma funziona senza JavaScript troppo.
dotnet add package mostlylucid.pagingtaghelper
Aggiungi a _ViewImports.cshtml:
@addTagHelper *, mostlylucid.pagingtaghelper
Attuazione IPagingModel<T> E hai finito:
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; }
}
Quello che ottieni
L'helper tag genera collegamenti che mantengono stringhe di query, supportano le intestazioni personalizzate e si integrano senza soluzione di continuità con HTMX (vedi l'esempio sopra).
Ecco come si integra l'intero sistema:
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 ha aggiunto un corretto rendering parziale di template nella versione 6.0 (dicembre 2024) con frammenti di template. Prima di allora, gli sviluppatori di Django usavano tipicamente tag di inclusione o pacchetti di terze parti come django-render-block. ASP.NET Core ha avuto PartialView() dalla versione 1.0 nel 2016 - framework diversi, timeline diverse, ma la stessa destinazione: frammenti HTML per HTMX.
Ruby on Rails ha Turbo Frames (parte di Hotwire), che è simile nello spirito:
<%= turbo_frame_tag "posts" do %>
<%= render @posts %>
<% end %>
La differenza è che Turbo richiede specifici frame marker su richiesta e risposta. HTMX è più flessibile - qualsiasi endpoint può restituire qualsiasi HTML, e si decide dove va con hx-target.
Phoenix LiveView di Elixir adotta un approccio diverso con connessioni WebSocket persistenti e stato lato server:
def handle_event("load_more", _params, socket) do
{:noreply, assign(socket, posts: load_more_posts())}
end
LiveView è brillante per le applicazioni in tempo reale, ma richiede l'infrastruttura WebSocket e la memoria del server per le connessioni. HTMX utilizza semplice vecchio HTTP - apolide, cacheable, scalabile. Per un blog, questo è perfetto.
Caching di output: La OutputCache attributo varia per hx-request intestazione, caching pagine intere e parziali separatamente:
[OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request", "pagerequest" })]
Efficienza della rete: Server-rendered HTML è spesso più piccolo di JSON + client-side template, richiede meno viaggi, e cache correttamente.
Dimensione del bundle: HTMX (14KB) + optional Alpine.js (15KB) + paging tag helper (0KB, lato server) = sotto i 30KB totali. Confronta quello con una tipica applicazione React (200KB+).
Aggiornamenti ottimizzati dell'interfaccia utente - Combina HTMX e Alpine per un feedback immediato:
<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>
Il conteggio aggiorna immediatamente (ottimistico), quindi sincronizza con la risposta del server.
Scambi fuoribordo - Aggiornare più sezioni di pagina da una risposta:
<div id="main-content"><!-- Main response --></div>
<div id="notification-count" hx-swap-oob="true"><span>5 new</span></div>
Perfetto per i badge di notifica, per il conteggio dei carrelli, ecc. Copro questo modello in profondità Mostrando Toast e Scambi con HTMX.
Modelli Client-Side con WebAPI - A volte si desidera un'esperienza intermedia: HTML fornito dal server per la maggior parte delle cose, ma JSON da una WebAPI per specifici contenuti dinamici. estensione client-side-templates ti permette di fare esattamente questo:
<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>
Questo approccio funziona con i modelli di Baffetti, Manubri o Nunjucks. Il WebAPI restituisce JSON, ma HTMX gestisce il lato client rendering. È particolarmente utile quando si dispone di un'API esistente o è necessario condividere i dati con le applicazioni mobili. Per maggiori dettagli sulle estensioni HTMX, tra cui client-side-template, vedere l'articolo di accompagnamento.
Strumento di debug: Installare il Estensione debug HTMX - mostra ogni richiesta, risposta e scambio in tempo reale.
I token antiforgery di ASP.NET Core necessitano di un trattamento speciale con AJAX. HTMX.NET fornisce diverse opzioni pulite. HtmxAntiforgeryScriptEndpoint:
// In Program.cs
app.MapHtmxAntiforgeryScript();
<!-- In your layout head -->
<script src="@HtmxAntiforgeryScriptEndpoints.Path" defer></script>
O utilizzare l'helper tag: <meta name="htmx-config" includeAspNetAntiforgeryToken="true" />. Vedi L'articolo di Khalid su HTMX Anti-Forgery Tokens per i dettagli completi.
Alpine'sCity name (optional, probably does not need a translation) @click cortocircuito in conflitto con il rasoio @ sintassi. Usare invece la forma esplicita:
<button x-on:click="doSomething()">Click me</button> <!-- Instead of @click -->
<div x-bind:class="isOpen ? 'block' : 'hidden'"></div> <!-- Instead of :class -->
O scappare con @@click, ma la sintassi esplicita è più chiara.
Controllo delle voci della cronologia: Per impostazione predefinita, HTMX spinge ogni richiesta alla cronologia. Per pagine/filtri dove non si desidera inquinamento della cronologia:
<paging model="@Model" hx-push-url="false"> <!-- Don't add history entries -->
O usare hx-replace-url="true" per aggiornare l'URL senza aggiungere voci.
Il problema del Fantasma Parziale: Il browser indietro/avanti mostra solo un frammento parziale senza layout. hx-history-elt sul contenitore contenuto:
<div class="container mx-auto" id="contentcontainer" hx-history-elt>
@RenderBody()
</div>
Questo indica a HTMX quale elemento da istantanea, preservando il layout circostante durante il ripristino della cronologia.
Sintoma: HTMX funziona localmente ma si rompe dietro una CDN - contenuti errati viene cached.
Causa principale: I CDN ignorano la HX-Request header. Il tuo server restituisce contenuti diversi in base a questo header, ma il CDN li caches identicamente.
ASP.NET Core fix: Uso VaryByHeaderNames:
[OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request" })]
Correttore CDN: Configura le regole della cache da includere HX-Request nella chiave cache. Per Cloudflare: Cruscotto → Caching → Regole cache → Chiave cache personalizzata → Intestazioni → Includi HX-Request.
hx-boost converte i collegamenti normali in richieste AJAX. Pagina delle eccellenze HTMX notes some core team members recommend evitarlo (scartamenti <head> contenuto, colpisce la località di comportamento), mentre altri trovano bene per le vittorie rapide.
<div hx-boost="true" hx-target="#contentcontainer">
<a asp-action="Show" asp-route-slug="@post.Slug">Read More</a>
</div>
Questo blog lo usa ampiamente. Quando si punta a contenitori specifici, esplicito hx-get è più chiaro, ma hx-boost funziona se sei coerente. Disabilita selettivamente con hx-boost="false" sugli elementi per bambini.
HTMX con ASP.NET Core parziales rappresenta un ritorno alla semplicità server-side senza sacrificare UX moderno. Ottieni:
L'approccio basato sul server ha resistito alla prova del tempo, e con HTMX, finalmente ha l'elegante tooling client-side che merita. È possibile costruire applicazioni web robuste e performanti utilizzando modelli che hanno funzionato per decenni - hanno appena aspettato lo strumento giusto per farli brillare di nuovo.
Questo articolo fa parte di una serie in due parti su HTMX con ASP.NET Core:
Documentazione ufficiale:
Strumenti e librerie:
Risorse comunitarie:
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.