HTMX och ASP.NET Core (Svenska (Swedish))

HTMX och ASP.NET Core

Comments

NOTE: Apart from English (and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.

Saturday, 03 May 2025

//

14 minute read

Inledning

HTMX är ett kraftfullt JavaScript-bibliotek som låter dig skapa dynamiska webbapplikationer med minimal JavaScript. Det gör att du kan göra AJAX-förfrågningar, byta HTML-innehåll och hantera händelser direkt i dina HTML-attribut. Jag har använt HTMX i ungefär två år vid denna tidpunkt och med varje projekt lär jag mig mer och mer om dess kapacitet; och ännu viktigare är att det är begränsningar.

Evenemang

flowchart LR subgraph Request Preparation A[htmx:configRequest] --> B[htmx:confirm] --> C[htmx:prompt] --> D[htmx:abort] end subgraph Request Lifecycle D --> E[htmx:beforeRequest] --> F[htmx:request] --> G[htmx:afterRequest] end subgraph Response Handling G --> H[htmx:beforeOnLoad] --> I[htmx:onLoad] I --> J[htmx:beforeSwap] --> K[htmx:swap] --> L[htmx:afterSwap] L --> M[htmx:afterSettle] --> N[htmx:afterOnLoad] end

Som du kan se tillhandahåller HTMX ett antal händelser som du kan koppla in för att ändra begäran eller svar. Med hjälp av var och en av dessa kan du ändra hur HTMX interagerar med servern / klienten på ganska omfattande sätt.

Utökningar

En av de mest kraftfulla aspekterna av HTMX är förmågan att skapa utökningar Att utöka dess kapacitet. I mitt fall, jag vanligtvis krok in htmx:configRequest lägga till ytterligare parametrar till begäran. Detta är användbart när du vill skicka ytterligare data till servern utan att behöva ändra HTML- eller JavaScript-koden.

Andra förlängningar kan kroka htmx:beforeRequest ändra begäran innan den skickas, men efter de flesta andra förlängningar som krok configRequest; som i beforeRequest saker som HX-Vals och HX-Includes är redan fästa vid reuest (antingen i nyttolasten \ frågesträngen). Du kan till och med kroka htmx:afterSwap utföra åtgärder efter att innehållet har bytts ut. Kombinerat med klientsidan templating bibliotek som Alpina.js eller Stång av järn eller olegerat stål, varmvalsad, varmdragen eller varmsträngpressad men inte vidare bearbetad du kan skapa kraftfulla dynamiska applikationer med minimal kod.

HTMX ger några inbyggda tillägg som hx-boost och hx-swap-oob vilket gör att du kan förbättra funktionaliteten hos HTMX utan att skriva någon anpassad kod. Det finns dock tillfällen då du behöver skapa dina egna förlängningar för att uppfylla specifika krav.

Till exempel kanske du vill lägga till egna headers till dina önskemål, ändra begäran nyttolast, eller hantera specifika händelser på ett unikt sätt.

För att uppnå detta HTMX ger dig några praktiska integrationspunkter:


{
  /**
   * init(api)
   * Called once when the extension is initialized.
   * Use it to set up internal state, store references, or access HTMX utility functions via the api parameter.
   */
  init: function(api) {
    return null;
  },

  /**
   * getSelectors()
   * Returns additional CSS selectors that HTMX should monitor.
   * Useful if your extension needs to handle custom elements or dynamic behavior.
   */
  getSelectors: function() {
    return null;
  },

  /**
   * onEvent(name, evt)
   * Called on every HTMX event (e.g., htmx:beforeRequest, htmx:afterSwap).
   * Return false to cancel the event or stop propagation.
   */
  onEvent: function(name, evt) {
    return true;
  },

  /**
   * transformResponse(text, xhr, elt)
   * Modify the raw response text before it is parsed and swapped into the DOM.
   * Use this to sanitize or preprocess HTML.
   */
  transformResponse: function(text, xhr, elt) {
    return text;
  },

  /**
   * isInlineSwap(swapStyle)
   * Return true if your extension will handle this swap style manually.
   * This tells HTMX to skip default behavior.
   */
  isInlineSwap: function(swapStyle) {
    return false;
  },

  /**
   * handleSwap(swapStyle, target, fragment, settleInfo)
   * Perform custom DOM manipulation if you implement a custom swap style.
   * Return true to prevent HTMX's default swap.
   */
  handleSwap: function(swapStyle, target, fragment, settleInfo) {
    return false;
  },

  /**
   * encodeParameters(xhr, parameters, elt)
   * Modify or serialize request parameters before sending.
   * Return null to use default URL/form encoding.
   * Return a string to override with a custom payload (e.g., JSON).
   */
  encodeParameters: function(xhr, parameters, elt) {
    return null;
  }
}

HTMX tillhandahåller ett antal förbyggda tillägg som du kan Läs om här.

Till exempel en praktisk Inbyggd förlängning json-encode låter dig skicka JSON data i begäran kroppen istället för URL-kodade formulär data. Detta är användbart när du vill skicka komplexa datastrukturer eller matriser till servern. Du kan se att detta krokar in i 3 händelser

  • init - att ställa in utökningen och lagra en hänvisning till HTMX API
  • onEvent - för att ställa in Content-Type Rubrik till application/json när begäran är konfigurerad
  • encodeParameters - att åsidosätta standard URL-kodad formulärkodning och serialisera parametrarna som JSON. Den returnerar också en sträng för att förhindra HTMX från att använda den förvalda URL-kodade formulärkodningen.
(function() {
  let api
  htmx.defineExtension('json-enc', {
    init: function(apiRef) {
      api = apiRef
    },

    onEvent: function(name, evt) {
      if (name === 'htmx:configRequest') {
        evt.detail.headers['Content-Type'] = 'application/json'
      }
    },

    encodeParameters: function(xhr, parameters, elt) {
      xhr.overrideMimeType('text/json')

      const object = {}
      parameters.forEach(function(value, key) {
        if (Object.hasOwn(object, key)) {
          if (!Array.isArray(object[key])) {
            object[key] = [object[key]]
          }
          object[key].push(value)
        } else {
          object[key] = value
        }
      })

      const vals = api.getExpressionVars(elt)
      Object.keys(object).forEach(function(key) {
        // FormData encodes values as strings, restore hx-vals/hx-vars with their initial types
        object[key] = Object.hasOwn(vals, key) ? vals[key] : object[key]
      })

      return (JSON.stringify(object))
    }
  })
})()

Eller till och med de enklare men ännu mer hångliga hx-debug förlängning som lägger till en HX-Debug Huvudet till begäran. Detta är användbart för felsökning och loggning, eftersom det gör att du kan se rå begäran och svarsdata i dev-konsolen.

(function() {
  htmx.defineExtension('debug', {
    onEvent: function(name, evt) {
      if (console.debug) {
        console.debug(name, evt)
      } else if (console) {
        console.log('DEBUG:', name, evt)
      } else {
        throw new Error('NO CONSOLE SUPPORTED')
      }
    }
  })
})()

Det finns många fler, inklusive en mycket kraftfull utbyggnad av klientsidans templatering vilket gör att du kan använda klient-side templating bibliotek för att omvandla returnerade JSON data till HTML. Detta är användbart för att skapa dynamiska användargränssnitt utan att behöva förlita sig på server-side rendering.

Några egna tillägg

Dynamiska radidentifikationer

Till exempel i ett nytt projekt använde jag HTMX OOB-swappar för att uppdatera ett antal rader i en tabell. För att göra detta ville jag veta vilka rader som för närvarande visas i tabellen, så jag uppdaterade bara de rader som var synliga.

Förlängning

export default {
    encodeParameters: function (xhr, parameters, elt) {
        const ext = elt.getAttribute('hx-ext') || '';
        if (!ext.split(',').map(e => e.trim()).includes('dynamic-rowids')) {
            return null; // Use default behavior
        }

        const id = elt.dataset.id;
        const approve = elt.dataset.approve === 'true';
        const minimal = elt.dataset.minimal === 'true';
        const single = elt.dataset.single === 'true';

        const target = elt.dataset.target;
        const payload = { id, approve, minimal, single };

        if (approve && target) {
            const table = document.querySelector(target);
            if (table) {
                const rowIds = Array.from(table.querySelectorAll('tr[id^="row-"]'))
                    .map(row => row.id.replace('row-', ''));
                payload.rowIds = rowIds;
            }
        }

        // Merge payload into the parameters object
        Object.assign(parameters, payload);
        return null; // Return null to continue with default URL-encoded form encoding
    }
}

Använda den

För att använda den måste vi lägga till tillägget till vår HTMX-konfiguration. Så i din ingångspunkt js-fil (förutsatt att du använder moduler; joy bör vara) kan du göra något sådant här:

import dynamicRowIds from "./dynamicRowIds"; // Load the file

htmx.defineExtension("dynamic-rowids", dynamicRowIds); // Register the extension

Sedan på vilket element du vill använda det på du kan lägga till hx-ext attribut med värdet dynamic-rowids.

                <button
                    hx-ext="dynamic-rowids"
                    data-target="#my-table"
                    data-id="@Model.Id"
                    data-param1="true"
                    data-param2="false"
                    data-param3="@Model.Whatever"
                    hx-post
                    hx-controller="Contoller"
                    hx-action="Action"
                >
                    <i class='bx bx-check text-xl text-white'></i>
                </button>

Bevara paramer

Detta är en annan enkel HTMX förlängning, denna tid ansluten till htmx:configRequest eftersom vi ändrar webbadressen innan begäran skickas. Denna förlängning är praktiskt om du använder frågesträng baserad filtrering etc. och du vill att vissa förfrågningar ska bevara befintliga filter medan andra inte (t.ex. namn och "startdatum" men inte "sida" eller "sort").

Detta är SIMILAR till men inte exakt samma som den befintliga HTMX-tillägg Prövningsparamor

Förlängning

Du kan se att vi krokar onEvent för att lyssna på htmx:configRequest händelse.

Då gör vi så här:

  • Hämta elementet som utlöste händelsen
  • Hämta preserve-params-exclude attribut från elementet (om det finns) och dela upp det i en rad nycklar för att utesluta (så vi inte lägga till dem i begäran)
  • Hämta aktuella URL-parametrar från fönstrets plats
  • Hämta de nya parametrarna från webbadressen för begäran
  • Loop genom de nuvarande parametrarna och kontrollera om de inte finns i listan med uteslutningar och inte redan i de nya parametrarna
  • Om de inte är det, lägger vi till dem till de nya parametrarna
  • Slutligen ställer vi in de nya parametrarna till begäran URL och återvänder sant att fortsätta med begäran.
export default {
    onEvent: function (name, evt) {
        if (name !== 'htmx:configRequest') return;
        const el = evt.detail.elt;
        const excludeStr = el.getAttribute('preserve-params-exclude') || '';
        const exclude = excludeStr.split(',').map(s => s.trim());

        const currentParams = new URLSearchParams(window.location.search);
        const newParams = new URLSearchParams(evt.detail.path.split('?')[1] || '');

        currentParams.forEach((value, key) => {
            if (!exclude.includes(key) && !newParams.has(key)) {
                newParams.set(key, value);
            }
        });

        evt.detail.path = evt.detail.path.split('?')[0] + '?' + newParams.toString();

        return true;
    }
};

Här använder jag det väsentliga HTMX.Net för sina taggars hjälpare. Såna som hx-controller och hx-action är tagghjälpare som genererar rätt HTMX-attribut för dig. Såväl som hx-route-<x> för värden att passera i rutten. Detta är verkligen användbart eftersom det tillåter dig att använda C#-kod för att generera rätt värden för attributen istället för att behöva hårdkoda dem i din HTML.

Använda den

Att vara en förlängning det är verkligen enkelt att använda:

Först måste vi lägga till tillägget till vår HTMX-konfiguration.


import preserveParams from './preserveParams.js';
htmx.defineExtension('preserve-params', preserveParams);

OBS: Du kommer att märka att standard HTMX-tilläggen använder "autoload"-metoden för att ladda förlängningen.

// Autoloading the extension and registering it
(function() {
  htmx.defineExtension('debug', {
}

Detta är ett bra sätt att göra det på om du använder HTMX i en icke-modul miljö. Men om du använder moduler (som du bör vara) är det bättre att använda import uttalande för att ladda förlängningen sedan uttryckligen registrera den mot din htmx till exempel. Detta gör att du kan dra nytta av trädskakning och bara ladda förlängningar du behöver.

Sedan på ditt element kan du lägga till hx-ext attribut med värdet preserve-params och preserve-params-exclude attribut med en kommaseparerad lista över parametrar som ska uteslutas från begäran.


<a class="btn-outline-icon"
   hx-controller="MyController"
   hx-action="MyAction"
   hx-route-myparam="@MyParam"
   hx-push-url="true"
   hx-ext="preserve-params"
   preserve-params-exclude="page,sort"
   hx-target="#page-content"
   hx-swap="innerHTML show:top"
   hx-indicator>
    <i class="bx bx-search"></i>
</a>

I detta fall på grund av event.detail.path med den nya myparam värde ställa in det i det kommer att ersätta den med vårt nya värde men bevara alla andra (utom page och sort).............................................................................................. Så vi kan fortsätta att skicka alla filter vi har ställt in i webbadressen till servern utan att behöva oroa sig för att de går förlorade när vi gör en ny begäran.

ASP.NET Kärna

En av de snygga sakerna med HTMX är att mycket av dess interaktion med servern sker via HTTP-huvuden. Dessa rubriker ger servern ett rikt sammanhang om vad som utlöste begäran, så att du kan svara på lämpligt sätt från dina ASP.NET Core-slutpunkter eller Razor-vyer.

Återigen en viktig del av detta är HTMX.Net....................................... Bland många av de objekt den ger är prydliga Request tillägg för att upptäcka HTMX-förfrågningar. Detta är användbart för att avgöra om en begäran gjordes av HTMX eller inte, och för att hantera den därefter.

Den har också sin egen mekanism för att skicka utlösningsdon


Response.Htmx(h => {
    h.WithTrigger("yes")
     .WithTrigger("cool", timing: HtmxTriggerTiming.AfterSettle)
     .WithTrigger("neat", new { valueForFrontEnd= 42, status= "Done!" }, timing: HtmxTriggerTiming.AfterSwap);
});

Tryck Urls etc... etc... Khalid gjorde ett bra jobb med att skapa en uppsättning tillägg för att göra det enkelt att arbeta med HTMX i ASP.NET Core.

Det är ett nyckelverktyg i min verktygslåda när du arbetar med HTMX och ASP.NET Core. Kolla!

Vanliga HTMX-förfrågan-huvuden

Här är en uppdelning av de mest användbara rubriker HTMX skickar med varje begäran:

på huvud med beskrivning på plats |----------------------------|------------------------------------------------------------------------------------------------------------------| på HX-request. Alltid inställd på true för alla HTMX-initierade begäran. Perfekt för att upptäcka HTMX-samtal i middleware eller regulatorer. Om ja, se till att det inte finns några tecken på att det inte finns någon anledning att tro att det inte finns någon anledning att tro att det inte finns någon anledning till det. Iden om målelementet i DOM som svaret kommer att bytas in i. Om ja, se till att det inte finns några tecken på att det inte finns någon anledning att tro att det inte finns någon anledning att tro att det inte finns någon anledning till det. Id för det element som utlöste begäran (t.ex. en knapp). Om ja, se till att det inte finns några tecken på att det inte finns någon anledning att tro att det inte finns någon anledning att tro att det inte finns någon anledning till det. på HX-Trigger-Name på det utlösande elementets namn (användbart för formulär). Om ja, se till att det inte finns några tecken på att det inte finns någon anledning att tro att det inte finns någon anledning att tro att det inte finns någon anledning till det. på HX-Prompt. Innehåller användarinmatning från hx-prompt. Om ja, se till att det inte finns några tecken på att det inte finns någon anledning att tro att det inte finns någon anledning att tro att det inte finns någon anledning till det. på HX-current-URL på webbläsaren URL när begäran initierades. Användbar för loggning och sammanhang. Om ja, se till att det inte finns några tecken på att det inte finns någon anledning att tro att det inte finns någon anledning att tro att det inte finns någon anledning till det. på HX-Historia-Restore-Request på engelska Skickas som sant om begäran är en del av en historik restaurering efter navigering (t.ex., tillbaka knapp). Om ja, se till att det inte finns några tecken på att det inte finns någon anledning att tro att det inte finns någon anledning att tro att det inte finns någon anledning till det.

Begäran om förlängningar

Jag använder dessa ganska mycket i mina ASP.NET Core-appar. Kombinerat med HTMX.Net sådana som Request.IsHtmx() , Request.IsHtmxBoosted() och Request.IsHtmxNonBoosted() Du kan enkelt upptäcka HTMX-förfrågningar och svara därefter.

Till exempel, Jag har en riktigt enkel förlängning till begäran som låter mig upptäcka om en begäran riktar sig till min huvudsakliga #page-content Div. Jag vet inte. Om det är då vet jag att jag borde skicka tillbaka en del. OBS: Många inser inte att du kan ange en "fullständig sida" som en partiell, det bara hoppar över layouten.

        if (Request.PageContentTarget())
        {   
            Response.PushUrl(Request);
            return PartialView("List", vm);
        }
        return View("List", vm);
        
        public static class RequestExtensions
{
        
        public static bool PageContentTarget(this HttpRequest request)
    {
        bool isPageContentTarget = request.Headers.TryGetValue("hx-target", out var pageContentHeader) 
                                   && pageContentHeader == "page-content";
        
        return isPageContentTarget;
    }
    }

Utökningar av svar

Förutom att begära förlängningar, kan du också skapa svar förlängningar för att skicka händelser tillbaka till klienten. Detta är användbart för att utlösa kundsidan händelser.

Söta alerta exempel

Till exempel i min SweetAlert2 integration Jag aktiverar att stänga dialogrutan med en utlösare som är inställd från servern.

    document.body.addEventListener('sweetalert:close', closeSweetAlertLoader);

Detta utlöses från servern som en HTMX utlösande händelse.


    public static void CloseSweetAlert(this HttpResponse response)
    {
        response.Headers.Append("HX-Trigger" , JsonSerializer.Serialize(new
        {
            sweetalert = "close"
        }));

    }

Detta kommer att utlösa sweetalert:close händelse på klientens sida, så att du kan stänga dialogrutan. Du kan också skicka data tillbaka till klienten med hjälp av HX-Trigger Huvudet. Detta är användbart för att överföra data från servern till klienten utan att behöva ändra HTML- eller JavaScript-koden.

Som ni ser är det mycket lätt att lyssna på dessa händelser genom att bara lägga till en händelselyssnare till kroppen. Jag använder JSON främst som det alltid kodar korrekt.

Visa toast

Jag skrev om min rostat bröd metod tidigare här, men det är värt att nämna här också. För att helt enkelt göra det möjligt för servern att utlösa en rostat bröd anmälan på klientens sida. Jag satte avtryckaren i denna svarsförlängning.

    public static void ShowToast(this HttpResponse response, string message, bool success = true)
    {
        response.Headers.Append("HX-Trigger", JsonSerializer.Serialize(new
        {
            showToast = new
            {
                toast = message,
                issuccess =success
            }
        }));

    }

Jag tar mig sedan in på evenemangsklientens sida och ringer min showToast Funktion.

import { showToast, showHTMXToast } from './toast';

window.showHTMXToast = showHTMXToast;

document.body.addEventListener("showToast", showHTMXToast);

Detta kallar sedan in min showToast funktion och väl, visar en skål; igen se mer om det i artikeln .



export function showHTMXToast(event) {
    const xhr = event?.detail?.xhr;
    let type = 'success';
    let message = xhr?.responseText || 'Done!';

    try {
        const data = xhr ? JSON.parse(xhr.responseText) : event.detail;

        if (data.toast) message = data.toast;
        if ('issuccess' in data) {
            type = data.issuccess === false ? 'error' : 'success';
        } else if (xhr?.status >= 400) {
            type = 'error';
        } else if (xhr?.status >= 300) {
            type = 'warning';
        }

    } catch {
        if (xhr?.status >= 400) type = 'error';
        else if (xhr?.status >= 300) type = 'warning';
    }

    showToast(message, 3000, type);
}

Slutsatser

Det var allt, en virvelvindsturné av HTMX och ASP.NET Core. Jag hoppas att du fann det användbart och informativt. Om du har några frågor eller kommentarer är du välkommen att kommentera nedan.

logo

©2024 Scott Galloway