https://github.com/rameel/ramstack.htmxtoolkit
Enables seamless integration of HTMX with ASP.NET Core (https://htmx.org).
https://github.com/rameel/ramstack.htmxtoolkit
aspnetcore htmx
Last synced: 11 months ago
JSON representation
Enables seamless integration of HTMX with ASP.NET Core (https://htmx.org).
- Host: GitHub
- URL: https://github.com/rameel/ramstack.htmxtoolkit
- Owner: rameel
- License: mit
- Created: 2024-02-08T20:44:49.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2025-06-14T20:27:53.000Z (12 months ago)
- Last Synced: 2025-06-14T20:54:53.243Z (12 months ago)
- Topics: aspnetcore, htmx
- Language: C#
- Homepage:
- Size: 127 KB
- Stars: 12
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# HtmxToolkit
[](https://nuget.org/packages/Ramstack.HtmxToolkit)
[](https://github.com/rameel/ramstack.htmxtoolkit/blob/main/LICENSE)
Provides HTMX integration for ASP.NET Core applications.
* [Getting Started](#getting-started)
* [HttpRequest](#httprequest)
* [HtmxRequestAttribute](#htmxrequestattribute)
* [HttpResponse](#httpresponse)
* [The declarative way of setting response headers](#the-declarative-way-of-setting-response-headers)
* [TagHelpers](#taghelpers)
* [HtmxUrlTagHelper](#htmxurltaghelper)
* [HtmxHeaderTagHelper](#htmxheadertaghelper)
* [HtmxConfigTagHelper](#htmxconfigtaghelper)
* [Antiforgery Token](#antiforgery-token)
* [Supported Versions](#supported-versions)
* [Contributions](#contributions)
* [Changelog](#changelog)
* [1.2.2](#122)
* [1.2.1](#121)
* [1.2.0](#120)
* [1.1.0](#110)
* [License](#license)
## Getting Started
Install `Ramstack.HtmxToolkit` [NuGet package](https://www.nuget.org/packages/Ramstack.HtmxToolkit/) to your project,
use the following command
```console
dotnet add package Ramstack.HtmxToolkit
```
## HttpRequest
The library provides a set of classes for working with `HttpRequest`.
First off, there's the `HttpRequestExtensions` class.
```csharp
///
/// Provides extension methods for the class.
///
public static class HttpRequestExtensions
{
///
/// Determines whether the specified HTTP request is htmx request.
///
/// The HTTP request.
///
/// true if the specified HTTP request is htmx request; otherwise, false.
///
public static bool IsHtmxRequest(this HttpRequest request);
///
/// Determines whether the specified HTTP request is htmx request.
///
/// The HTTP request.
/// When this methods returns, contains the
/// that provides well-known htmx headers.
///
/// true if the specified HTTP request is htmx request; otherwise, false.
///
public static bool IsHtmxRequest(this HttpRequest request, out HtmxRequestHeaders headers);
///
/// Determines whether the specified HTTP request was made using AJAX
/// instead of a normal navigation.
///
/// The HTTP request.
///
/// true if the specified HTTP request is boosted; otherwise, false.
///
public static bool IsHtmxBoosted(this HttpRequest request);
///
/// Determines whether the specified HTTP request was made using AJAX
/// instead of a normal navigation.
///
/// The HTTP request.
/// When this methods returns, contains the
/// that provides well-known htmx headers.
///
/// true if the specified HTTP request is boosted; otherwise, false.
///
public static bool IsHtmxBoosted(this HttpRequest request, out HtmxRequestHeaders headers);
///
/// Returns the that provides well-known htmx headers.
///
/// The HTTP request.
///
/// The .
///
public static HtmxRequestHeaders GetHtmxHeaders(this HttpRequest request);
}
```
The `IsHtmxRequest` method allows you to determine whether the current request is initiated by HTMX.
```csharp
HttpContext.Request.IsHtmxRequest()
```
And thus, you can define different scenarios depending on the result, for example:
```csharp
if (Request.IsHtmxRequest())
return PartialView();
return View();
```
The overloads of these methods provide access to strongly typed headers set by HTMX:
```csharp
if (Request.IsHtmxRequest(out var headers))
{
if (headers.HistoryRestoreRequest)
{
...
}
}
```
In addition, access to strongly typed headers can be obtained by calling `GetHtmxHeaders`:
```csharp
var headers = Request.GetHtmxHeaders();
```
The full list of headers is presented below:
```csharp
///
/// Represents strongly typed HTMX request headers.
///
public readonly struct HtmxRequestHeaders
{
///
/// Gets a value indicating whether the request
/// was made using AJAX instead of a normal navigation.
///
public bool Boosted { get; }
///
/// Gets the current URL of the browser.
///
public string? CurrentUrl { get; }
///
/// Gets a value indicating whether the request
/// is for history restoration after a miss in the local history cache.
///
public bool HistoryRestoreRequest { get; }
///
/// Gets the user response to an hx-prompt on the client.
///
public string? Prompt { get; }
///
/// Gets a value indicating whether the current request is htmx request.
///
public bool Request { get; }
///
/// Gets the ID of the target element if it exists.
///
public string? Target { get; }
///
/// Gets the name of the triggered element if it exists.
///
public string? TriggerName { get; }
///
/// Gets the ID of the triggered element if it exists
/// as indicated by the HX-Trigger header.
///
public string? Trigger { get; }
}
```
Example of usage:
```csharp
if (Request.GetHtmxHeaders().HistoryRestoreRequests)
{
...
}
```
The library also provides a set of predefined string constants for request headers,
so you don't have to remember them each time and risk making mistakes in spelling.
You can find them in the `HtmxRequestHeaderNames` class.
```csharp
///
/// Defines constants for the well-known names of htmx request headers.
/// For more information, see https://htmx.org/reference/#request_headers
///
public static class HtmxRequestHeaderNames
{
///
/// The HX-Boosted header indicates whether the request was made using AJAX
/// instead of a normal navigation.
///
public const string Boosted = "HX-Boosted";
///
/// The HX-Current-URL header contains the current URL of the browser.
///
public const string CurrentUrl = "HX-Current-URL";
...
// The list of other constants is omitted for brevity
}
```
### HtmxRequestAttribute
To simplify some manual checks, the library provides an ASP.NET Core result filter
that can be applied to an action controller, page handler, or the entire controller:
```csharp
public class UserController : ControlleBase
{
[HtmxRequest]
public IActionResult UpdateProfile(UserProfile profile)
{
...
}
}
```
If you are processing boosted requests in a special way, add the `Boosted` parameter.
```csharp
public class UserController : ControlleBase
{
...
[HtmxRequest(Boosted = true)]
public IActionResult UpdateProfile(UserProfile profile)
{
...
}
}
```
## HttpResponse
For working with response headers, the library also provides a set of classes.
The first one is the `HttpResponseExtension` class with extension methods:
```csharp
///
/// Provides extension methods for the class.
///
public static class HttpResponseExtensions
{
///
/// Returns the that provides well-known htmx headers.
///
/// The HTTP response.
///
/// The .
///
public static HtmxResponseHeaders GetHtmxHeaders(this HttpResponse response);
///
/// Configures the htmx response headers.
///
/// The HTTP response to configure.
/// The function to configure the htmx response headers.
public static void Htmx(this HttpResponse response, Action configure);
///
/// Configures the htmx response headers.
///
/// The HTTP response to configure.
/// The function to configure the htmx response headers.
/// The value to pass to the .
public static void Htmx(this HttpResponse response, Action configure, TState state);
}
```
The `GetHtmxHeaders` method provides access to strongly typed response headers
that control HTMX behavior.
```csharp
///
/// Represents strongly typed HTMX response headers.
///
public sealed class HtmxResponseHeaders
{
///
/// Gets or sets the HX-Location header to a client-side redirect
/// that does not do a full page reload.
///
[MaybeNull]
public string Location { get; set; }
///
/// Gets or sets the HX-Push-Url header to push a new URL into the history stack.
///
[MaybeNull]
public string PushUrl { get; set; }
...
// The remaining properties are omitted for brevity
}
```
Just like `HtmxRequestHeaderNames`, which consists of predefined string constants for HTMX request headers,
there is a corresponding `HtmxResponseHeaderNames` class containing
a list of string constants for HTMX response headers.
```csharp
///
/// Defines constants for the well-known names of htmx response headers.
/// For more information, see https://htmx.org/reference/#response_headers
///
public static class HtmxResponseHeaderNames
{
///
/// The HX-Location header is used to a client-side redirect that does not do a full page reload.
///
public const string Location = "HX-Location";
///
/// The HX-Push-Url header is used to push a new URL into the history stack.
///
public const string PushUrl = "HX-Push-Url";
///
/// The HX-Redirect header is used to client-side redirect to a new location.
///
public const string Redirect = "HX-Redirect";
...
// The list of other constants is omitted for brevity
}
```
However, the most convenient and efficient way is by using one of the provided `Htmx` methods
with a callback that accepts `HtmxResponse`, allowing you to specify response headers
in a fluent style:
```csharp
Response.Htmx(h => h
.TriggerEvent(
eventName: "process",
detail: new { Value = ... })
.StopPolling(ShouldStopPolling));
```
:bulb: The second Htmx method accepts an additional parameter to avoid unnecessary allocations due to closures:
```csharp
Response.Htmx(
static (h, stop) => h
.TriggerEvent(
eventName: "process",
detail: new { Value = ... })
.StopPolling(stop),
ShouldStopPolling);
```
:bulb: The `Htmx` extension methods are also available for `IActionResult`, allowing you to write:
```csharp
return Json(profile).Htmx(h => h.StopPolling(ShouldStopPolling));
```
In both cases, the headers will be set only in the case of an `htmx` request.
In the case of a regular request, the callback passed to the `Htmx(this)` method will not be executed,
which allows avoiding unnecessary work.
### The declarative way of setting response headers
Some of the response headers can be set declaratively using the `HtmxResponseAttribute`,
which is applied to the controller, action, or page.
```csharp
public class UserController : ControlleBase
{
[HtmxRequest]
[HtmxResponse(
StopPolling = true,
Reswap = HtmxSwap.OuterHtml)]
public IActionResult UpdateProfile(UserProfile profile)
{
...
}
}
```
:bulb: If a more complex expression is needed for `swap`, for example, `innerHTML show:#result:top`,
you can use the `Reswap` method in the `HtmxResponse` class, which accepts a string.
```csharp
///
/// Sets the HX-Reswap header to specify how the response will be swapped.
///
/// The header value to set.
///
/// The current instance.
///
public HtmxResponse Reswap(string value);
///
/// Sets the HX-Reswap header to specify how the response will be swapped.
///
/// The header value to set.
///
/// The current instance.
///
public HtmxResponse Reswap(HtmxSwap value);
```
And for the `HtmxResponseAttribute`, there is the `ReswapExpression` property.
```csharp
///
/// Gets or sets the HX-Reswap header that allows to specify how the response will be swapped.
///
[MaybeNull]
public string ReswapExpression { get; set; }
///
/// Gets or sets the HX-Reswap header that allows to specify how the response will be swapped.
///
public HtmxSwap Reswap { get; set; }
```
allowing you to flexibly configure the `swap` header you need.
## TagHelpers
The library provides 3 tag helpers:
* `HtmxUrlTagHelper`
* `HtmxHeaderTagHelper`
* `HtmxConfigTagHelper`
To make them available in your project, add the `@addTagHelper` directive in the Razor view.
```razor
@addTagHelper *, Ramstack.HtmxToolkit
```
To make the tag helpers available globally for the entire application, you should add this line
to the `_ViewImports.cshtml` file, which is inherited by all view files by default.
### HtmxUrlTagHelper
The `HtmxUrlTagHelper` allows generating links for HTMX methods similar to how it's done in ASP.NET Core
for generating links, just replace the `asp-` prefix with the `hx-` prefix.
```html
Show Info
```
The following code will be generated:
```html
Show Info
```
By default, if no HTMX method is specified, `hx-get` is used. To specify a particular method,
you can choose one from the following attributes: `hx-get`, `hx-post`, `hx-put`, `hx-delete`, or `hx-patch`.
For instance, in the following example, we use `hx-post`:
```html
Show Info
```
In this case, the following code will be generated:
```html
Show Info
```
The following attributes are also available for obtaining links to a page and a page handler:
```html
Attendee Profile
```
The following code will be generated:
```html
Attendee Profile
```
Also, the `hx-all-route-data` attribute is available, which accepts a dictionary where
the key is the parameter name, and the value is the parameter value. In the example below,
a dictionary with specific parameters is created, which is then used as the value
of the `hx-all-route-data` attribute.
```html
@{
var parameters = new {
category = "science",
pdf = true
};
}
Books
```
The following code will be generated:
```html
Books
```
In addition to the examples mentioned, the following properties are also available:
* `hx-host`
* `hx-protocol`
* `hx-fragment`
### HtmxHeaderTagHelper
The `htmx` library allows adding custom headers that will be submitted with an AJAX request.
However, since JSON format should be used for this, writing it out manually is not always convenient,
especially considering the need to escape special characters. Fortunately, the library provides
the `HtmxHeaderTagHelper` class, which takes care of this and allows specifying headers
in a clearer and more readable format.
```html
Get Some HTML, Including A Custom Header in the Request
```
The following code will be generated:
```html
Get Some HTML, Including A Custom Header in the Request
```
Also, if you have a dictionary with the headers you need,
you can assign them to the `hx-all-headers` attribute:
```html
Get Some HTML, Including A Custom Header in the Request
```
The `HtmxHeaderTagHelper` will take care of all the remaining work regarding JSON serialization and escaping.
### HtmxConfigTagHelper
As with `hx-headers`, configuring `htmx` settings requires a JSON representation.
For working with configuration, the `HtmxConfigTagHelper` class is provided.
```html
```
The following code will be generated:
```html
```
If desired or for the purpose of semantics, you can use `htmx-config` as the standalone name of the element:
```html
```
## Antiforgery Token
If you have enabled the generation of the **Antiforgery** token in the configuration
(`include-antiforgery-token="true"`), then you need to include a small JavaScript file
that will ensure this token is present in form parameters or headers
and refresh it in a timely manner.
To do this, you can directly include the contents of the JavaScript file on the page:
```html
@Html.HtmxAntiforgeryScript()
```
Alternatively, to retrieve the debug version of the script,
you can pass the debug parameter with a value of `true`:
```html
@Html.HtmxAntiforgeryScript(debug: true)
```
The `debug` parameter determines which version will be included. By default, the minimized version
of the script will be returned, which weighs very little and takes up approximately 520 bytes.
The method returns a pre-initialized `HtmlString` with the script content,
so there will be no unnecessary conversions and allocations every time it's used.
Alternatively, you can register the corresponding endpoint for the script by calling:
```csharp
app.UseAuthorization();
...
app.MapHtmxAntiforgeryScript();
app.MapControllers();
```
By default, the registered path is mapped to `/htmxtoolkit/[sha1-hash]`,
where **[sha1-hash]** represents a precalculated hash of the script content.
This approach eliminates the need to worry about cache invalidating
when updating the script in the future as the hash automatically changes
when the script content is modified.
If you want to change the path to your own, specify this path in the parameter.
```csharp
app.MapHtmxAntiforgeryScript("/my-path");
```
Now, include it on the page.
```html
```
Alternatively, to retrieve the debug version of the script, you can pass the `debug` parameter
with a value of `true`, which instructs to include a query parameter `?debug` in the path.
The presence of this parameter determines the loading of the debug version:
```html
```
The `debug` parameter determines whether to load the minimized version (used by default)
or the debug version of the script.
## Supported Versions
| | Version |
|------|------------|
| .NET | 6, 7, 8, 9 |
## Contributions
Bug reports and contributions are welcome.
## Changelog
### 1.2.2
Explicitly pass `document` object to event listeners in `htmx-toolkit.js` to improve compatibility
### 1.2.1
Add `[DisallowNull]` attribute to `Reswap` property to disallow null input
### 1.2.0
* Add `AjaxContext` to align with the capabilities provided by htmx
* Add method overloads for `PushUrl` and `ReplaceUrl` that prevents URL changes (`PreventPushUrl` / `PreventReplaceUrl`)
* Add support `` element as a standalone HTML element
### 1.1.0
* Add overloads for IsHtmxRequest and IsHtmxBoosted methods enabling retrieval of htmx request headers
* Improve HtmxRequestAttribute
## License
This package is released as open source under the **MIT License**.
See the [LICENSE](https://github.com/rameel/ramstack.htmxtoolkit/blob/main/LICENSE) file for more details.