# HTMX扩展和与 ASP.NET 核心使用HTMX

<datetime class="hidden">2025-05-02T20:30</datetime>

<!--category-- Javascript, HTMX, ASP.NET Core -->
# 一. 导言 导言 导言 导言 导言 导言 一,导言 导言 导言 导言 导言 导言

HTMX 是一个强大的 JavaScript 图书馆, 它允许您创建动态的网络应用程序, 最小的 JavaScript 。 它允许您提出 AJAX 请求, 交换 HTML 内容, 并直接处理 HTML 属性中的事件 。 我使用 HTMX 已有两年左右的时间了, 并且每个项目我都越来越了解它的能力; 更重要的是它的局限性 。

但是,我仍然不声称对它有专家知识。我只是想分享一下我在旅途中学到的一些东西。

> **随行条款:** 本篇文章的重点是HTMX的事件系统、扩展和高级定制。关于部分浏览的 ASP.NET 核心集成模式, HTMX.NET 和页码见我的随附文章: [带有ASP.net核心部分的HTMXHTMX:服务器-Side文艺复兴](/blog/htmx-aspnetcore-partials).

[TOC]

# 事件事件事件事件

## 请求准备

请求准备阶段是HTMX在发送到服务器之前配置请求的阶段。 这包括设置信头、添加参数和处理用户输入。 在此阶段触发以下事件:

```mermaid
flowchart LR

        A[htmx:configRequest] --> B[htmx:confirm] --> C[htmx:prompt] --> D[htmx:abort]

```

## 请求寿命周期

请求的生命周期阶段是HTMX向服务器发送请求并处理回复的周期阶段。

```mermaid
flowchart LR
 E[htmx:beforeRequest] --> F[htmx:request] --> G[htmx:afterRequest]
```

## 反应处理

反应处理阶段是HTMX处理服务器响应并更新DOM的处理阶段。

```mermaid
flowchart LR
        H[htmx:beforeOnLoad] --> I[htmx:onLoad]
        I --> J[htmx:beforeSwap] --> K[htmx:swap] --> L[htmx:afterSwap]
        L --> M[htmx:afterSettle] --> N[htmx:afterOnLoad]

```

## 历史管理

HTMX 在历史管理阶段更新浏览器历史和 URL 。 在此阶段触发以下事件 :

```mermaid
flowchart LR
 A[htmx:historyRestoreRequest]
 A --> B[htmx:historyPopped]
 B --> C[htmx:historyRestorePage]
```

正如您可以看到 HTMX 提供了一些事件, 您可以连接到这些事件来修改请求或回应, 甚至历史; HTMX 是一个如此紧凑的系统, 做一个LOT 。 您可以使用其中的每一个来修改 HTMX 如何以非常全面的方式与服务器/ 客户端进行互动 。

# 延长

HTMX最强大的一个方面是: [创建扩展名](https://v2-0v2-0.htmx.org/extensions/) 来扩展它的能力。 `htmx:configRequest` 以在请求中添加额外参数。当您想要将额外数据传送到服务器而不必修改 HTML 或 JavaScript 代码时,此选项非常有用。

其它分机可能会被勾勾勾 `htmx:beforeRequest` (b) 在收到请求之前修改请求;但 **之后** 勾勾勾 `configRequest`; 如 的 `beforeRequest` 类似的东西 `HX-Vals` 和 `HX-Include`s 已经附在背面(在有效载荷\查询字符串中)。
你连勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾 `htmx:afterSwap` 在内容被互换后执行动作。 [阿尔卑山](https://alpinejs.dev/) 或 [文学](https://lit.dev/) 您可以在最小代码下创建强大的动态应用程序。

HTMX 提供一些内置扩展像 `hx-boost` 和 `hx-swap-oob` 允许您在不写任何自定义代码的情况下增强 HTMX 的功能。然而,有时您需要创建自己的扩展以达到特定要求。

例如,您可能需要在您的请求中添加自定义信头, 修改请求的有效载荷, 或者以独特的方式处理具体事件 。

实现HTMX为您提供一些方便的整合点:

```javascript

{
  /**
   * 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 提供一些预建扩展件 [有关在这里读取](https://htmx.org/extensions/).

举例说,便便 [内嵌扩展](https://github.com/bigskysoftware/htmx-extensions/blob/main/src/json-enc/README.md) `json-encode` 允许您在请求的正文中发送 JSON 数据, 而不是 URL 编码的窗体数据。 如果您想要向服务器发送复杂的数据结构或阵列, 这会有用 。
你可以看到,这个钩子连成3个事件

- `init` - 设置扩展范围并储存HTMX API的参考文献
- `onEvent` - 设置 `Content-Type` 标题到 `application/json` 当请求被配置时
- `encodeParameters` - 推翻默认 URL 编码格式编码,并将参数序列化为 JSON。它还返回一个字符串,以防止 HTMX 使用默认 URL 编码格式编码。

```javascript
(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))
    }
  })
})()
```

甚至更简单,但更简单 `hx-debug` 加上 `HX-Debug` 请求页头。 这可用于调试和记录目的, 因为它允许您在 Dev 控制台看到原始请求和响应数据 。

```javascript
(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')
      }
    }
  })
})()

```

还有更多,包括一个非常强大的 [客户端侧推演扩展名](https://github.com/bigskysoftware/htmx-extensions/tree/main/src/client-side-templates) 从而允许您使用客户端微博库将返回的 JSON 数据转换为 HTML 。 这对于创建动态 UI 有用, 不必依赖服务器端翻譯 。

# 一些自定义扩展名

## 动态行编号

例如,在最近的一个项目中,我利用HTMX OOB交换软件更新了表格中的若干行。
为了做到这一点,我想知道表格中目前显示的是哪些行,所以我只更新了可见的行。

### 延长

```javascript
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
    }
}
```

### 使用它

要使用它,我们需要在我们的 HTMX 配置中添加扩展名 。
所以在您输入点 js 文件( 假设您使用模块; yoy should be) 中, 您可以做类似的事情 :

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

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

然后,在任意元素上您想要使用它,您可以添加 `hx-ext` 值的属性 `dynamic-rowids`.

```html
                <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>

```

## 保留参数

这是另一个简单的 HTMX 扩展名, 这次连接到 `htmx:configRequest` 请求发送前我们正在修改 URL 。 如果您使用基于查询字符串的过滤等, 您想要一些请求来保存已有的过滤器, 而其他请求则不保存( 例如“ name ” 和 “ startdate ” , 但不使用“ page ” 或“ sort ” ) , 此扩展是有用的 。

这是SMIILAR, 与现有的 HTMX 扩展号不完全相同 [推进参数](https://github.com/bigskysoftware/htmx-extensions/blob/main/src/path-params/README.md)

### 延长

你可以看到我们勾勾勾 `onEvent` 收听 `htmx:configRequest` 事件 。

然后我们:

- 获取触发事件元素
- 获取 `preserve-params-exclude` 从元素属性( 如果存在的话) 中从属性中分离出来, 并将其分成一系列要排除的密钥( 因此我们不把它们添加到请求中) 。
- 从窗口位置获取当前 URL 参数
- 从请求 URL 获取新参数
- 环绕当前参数,检查当前参数是否在排除列表中,是否在新参数中
- 如果不是,我们把它们加到新的参数中
- 最后,我们为请求的 URL 设定了新的参数, 并返回真实, 以便继续请求 。

```javascript
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;
    }
};
```

在这里,我用的是必需的 [HTMX.Net](https://github.com/khalidabuhakmeh/Htmx.Net/tree/main) 其标签助手的标签。 `hx-controller` 和 `hx-action` 是为您生成正确的 HTMX 属性的标签助手。以及 `hx-route-<x>` 用于在路径中传递值。这非常有用,因为它允许您使用 C# 代码生成属性的正确值, 而不是在 HTML 中硬编码 。

### 使用它

作为一个扩展,它非常容易使用:

首先,我们需要在我们的 HTMX 配置中添加扩展 。

```javascript

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

注意: 您会注意到默认的 HTMX 扩展使用“ 自动装入” 方法装入扩展 。

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

如果您在非模块环境中使用 HTMX 。 但是, 如果您使用模块( 应该是模块) , 最好使用 `import` 将扩展的语句加载到扩展的语句,然后明确将其注册为与您的 `htmx` 复选框。这样,您就可以利用树的摇动,只装载所需的扩展。

然后,在你的元素上,你可以添加 `hx-ext` 值的属性 `preserve-params` 和 `preserve-params-exclude` 属性,带有逗号分隔的参数列表,从请求中排除。

```html

<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>
```

在本案中,由于 `event.detail.path` 新的 `myparam` 将替换为我们的新值, 但保留所有其他值( 除非 `page` 和 `sort`)这样,我们就可以不断将我们在 URL 中设置的任何过滤器传递到服务器上,而不必担心当我们提出新的请求时这些过滤器丢失。

# ASP.NET核心

HTMX的精美之处之一是,它与服务器的许多互动都通过 HTTP 信头发生。这些信头为服务器提供了内容丰富的关于触发请求的内容,允许您从 ASP.NET 核心端点或 Razor 视图中做出适当反应。

同样,这方面的一个关键组成部分是: [HTMX.Net](https://github.com/khalidabuhakmeh/Htmx.Net/tree/main)。在它提供的许多物品中,有些是整洁的。 `Request` 用于检测 HTMX 请求的延期。 这有助于确定 HTMX 是否提出请求, 并相应处理 。

它还有自己的机制来发送触发器

```csharp

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

推推器等... etc...
Khalid做了一个伟大的工作 创建了一系列的扩展 方便与HTMX合作 在ASP.NET核心。

这是我用HTMX和ASP.NET核心 工作的工具箱里的关键工具 [**检查出来!**](https://github.com/khalidabuhakmeh/Htmx.Net/tree/main)

## HTMX 通用 HTMX 请求信头

以下是最有用的信头 HTMX 发送的所有请求的细目 :

* 标题 * * 描述 *
|----------------------------|------------------------------------------------------------------------------------------------------------------|
HX- request {} 总是对 HTMX 启动的请求设定正确。  
HX-Target * DOM中目标元素的代号,该响应将被转换为 。 * * *
* HX-Trigger * 触发请求的元素(例如按钮)的代号 *
HX-Trigger-Name 触发元素的名称(可用于窗体)  \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \
HX-Prompt 包含来自 hx- prompt 的用户输入 。
请求启动时的浏览器 URL URL 。 用于记录和上下文 。
* HX-History-Restore-Restore-Request as seven as even if该请求是航行后历史恢复的一部分(例如后键) 。

## 请求延期

我在ASP.NET核心应用软件中广泛使用这些软件。 `Request.IsHtmx()` , `Request.IsHtmxBoosted()` 和 `Request.IsHtmxNonBoosted()` 您可以很容易地检测到HTMX要求并做出相应反应。

例如,我有一个非常简单的扩展名 请求,它让我可以检测 是否请求针对我的主 `#page-content` (div) 如果是这样的话,我知道我应该寄回一部分
注意: 许多人不知道你可以指定一个“ 全页”为部分, 然后跳过布局 。

```csharp
        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;
    }
    }

```

## 回应延长

除请求延期外,您还可以创建响应延期,将事件发回客户。这对启动客户附带活动有用。

### 甜快示例

例如 [在我的SweetAlert2整合中](https://www.mostlylucid.net/blog/usingsweetalertforhxindicators) 我允许使用服务器设定的触发器关闭对话框 。

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

```

这是由服务器作为 HTMX 触发事件触发的 。

```csharp

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

    }
```

这将触发 `sweetalert:close` 对话框。您也可以使用该对话框将数据传送给客户 `HX-Trigger` 头页。 它可用于将数据从服务器传送到客户端, 无需修改 HTML 或 JavaScript 代码 。

正如你所看到的那样,通过在身体上添加一个活动听众,听这些事件是很容易的。我主要使用JSON,因为它总是正确的编码。

### 显示汤点

以前我写过吐司方法 [在这里](https://www.mostlylucid.net/blog/showingtoastandswappingwithhtmx),但在此也值得一提。非常简单地说,使服务器能够触发客户方的敬酒通知。我设置了此响应扩展的触发程序。

```csharp
    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
            }
        }));

    }
```

然后我就勾入事件客户的侧面 打电话给我 `showToast` 函数。

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

window.showHTMXToast = showHTMXToast;

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

这又呼唤我, `showToast` 函数和 well, 显示一个举举; 再查看更多关于它的内容 [条款内 ](https://www.mostlylucid.net/blog/showingtoastandswappingwithhtmx).

```javascript


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);
}
```

## 在结论结论中

HTMX 和 ASP.NET Core 的旋风之旅,我希望你觉得它有用和丰富。如果你有任何问题或评论,请在下面随意评论。

## 本博客上的相关文章

### 随带条款

本篇文章是HTMX和ASP.NET Core的两部分系列的一部分:

1. **[带有ASP.net核心部分的HTMX](/blog/htmx-aspnetcore-partials)** - 侧重于ASP.NET核心整合、部分观点、HTMX.NET和页码
2. **本条本条本条** - 深入潜入HTMX事件、生命周期、扩展架构和定制扩展

### 更多HTMX文章

- [添加 HTMX 的呼叫](/blog/addpagingwithhtmx) - 使用HTMX实施射速
- [ASP.NET 与HTMX的核心缓冲](/blog/aspnetcachingwithhtmx) - 落实HTMX要求的缓冲战略
- [用阿尔卑斯山和HTMX自动更新](/blog/autorefreshwithalpineandhtmx) - 将阿尔卑斯山日与HTMX结合,用于反应组件
- [使用 HTMX 使您的站点更像 SPA](/blog/htmxtomakeyoursitemorespalike) - 没有 JavaScript 框架的和SPA 类似导航
- [使用 SweetAlert 用于 HX 指标](/blog/usingsweetalertforhxindicators) - 带HTMX的精美装载指标
- [使用 HTMX 显示通吐和互换](/blog/showingtoastandswappingwithhtmx) - 服务器触发吐司通知

## 继续阅读

**HTMX官方文件:**

- [HTMX文件文件](https://htmx.org/docs/) - HTMX正式文件
- [HTMX 事件参考](https://htmx.org/events/) - HTMX事件完整清单
- [HTMX 扩展](https://htmx.org/extensions/) - 正式HTMX延长
- [HTMX 示例](https://htmx.org/examples/) - HTMX模式实例
- [HTMX 论文](https://htmx.org/essays/) - 关于HTMX哲学的深思熟虑的文章

**图书馆和工具 :**

- [HTMX.NET 吉特Hub](https://github.com/khalidabuhakmeh/Htmx.Net) - HTMX.net图书馆源代码
- [HTMX.NET NUGet](https://www.nuget.org/packages/Htmx/) - HTMX. NUGet 网络
- [Khalid Abuhakmeh网站,](https://khalidabuhakmeh.com/) - HTMX.NET的创建者
- [Alpine.js 文档](https://alpinejs.dev/) - 官方阿尔卑斯高山医生

**社区资源:**

- [HTMX 差异](https://htmx.org/discord) - 积极的社区支助
- [超媒体系统书](https://hypermedia.systems/) - 免费在线书,内容为建立超多媒体应用程序