Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ HtmxToolkit lets you do all of this from C# and Razor, without header strings or
- Build `hx-get` and `hx-post` URLs from controllers, actions, and Razor Pages, just like the `asp-*` Tag Helpers do.
- Configure HTMX once in `Program.cs` and send antiforgery tokens with HTMX requests automatically.

HtmxToolkit runs on .NET 6 or later and supports HTMX 1.9.x, 2.x, and 4.x. HTMX 2.x is the default.
HtmxToolkit runs on .NET 6 or later and supports HTMX 1.9.x, 2.x, and 4.x.

## Getting started

Expand All @@ -36,10 +36,11 @@ from a CDN or as a local file.
```csharp
using Ramstack.HtmxToolkit.Hosting;

builder.Services.AddHtmxToolkit();
builder.Services.AddHtmxToolkit(options => options.UseHtmxV2());
```

By default, the toolkit targets HTMX 2.x. If you use HTMX 1.9.x or 4.x, select it as shown in [Configuration](#configuration).
Select the HTMX version your application loads: `UseHtmxV1`, `UseHtmxV2`, or `UseHtmxV4`.
The application does not start without it. See [Configuration](#configuration) for details.

**3. Enable the Tag Helpers in `_ViewImports.cshtml`:**

Expand Down
2 changes: 1 addition & 1 deletion docs/articles/antiforgery.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ See [Static assets and deployment](static-assets.md) for script URLs, caching, a

## Enable server-side validation

Sending a token and validating it are separate steps. `AddHtmxToolkit()` configures toolkit services;
Sending a token and validating it are separate steps. `AddHtmxToolkit` configures toolkit services;
it does not add an antiforgery validation filter or middleware to your application.

### Razor Pages
Expand Down
9 changes: 5 additions & 4 deletions docs/articles/choosing-version.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
HtmxToolkit adapts configuration, request options, and response event headers to the selected HTMX version.
Select the same major version that the browser loads.

HTMX 2.x is selected when no version is specified.
The selection is required. If `AddHtmxToolkit` selects no version, the application fails to start.
If `AddHtmxToolkit` is not called, the Tag Helpers that depend on the version and `TriggerEvent`
throw `InvalidOperationException` when they run.

## Select a version

Expand Down Expand Up @@ -38,7 +40,6 @@ trying to select a different version causes startup validation to fail.

| Behavior | HTMX 1.9.x | HTMX 2.x | HTMX 4.x |
|---------------------------|-------------------------------------------|-------------------------------------------|-------------------|
| Default when omitted | No | Yes | No |
| Global config type | `HtmxV1Config` | `HtmxV2Config` | `HtmxV4Config` |
| Per-request attribute | `hx-request` | `hx-request` | `hx-config` |
| Triggering element header | `HX-Trigger` | `HX-Trigger` | `HX-Source` |
Expand Down Expand Up @@ -70,8 +71,8 @@ Only settings you assign a value are included in the generated configuration. A
For an existing application, select the version it already loads and review [Version compatibility](version-compatibility.md) before upgrading.

For a new application with no version-specific requirements, start with HTMX 2.x to follow the introductory
tutorials without changes. It is HtmxToolkit's default target. Choose V4 when you want its native morphing
or Fetch API options, and check that the extensions you plan to use support it.
tutorials without changes. Choose V4 when you want its native morphing or Fetch API options,
and check that the extensions you plan to use support it.

Continue with [Application configuration](configuration.md), then use the dedicated [HTMX 1.9.x](configuration-v1.md),
[HTMX 2.x](configuration-v2.md), or [HTMX 4.x](configuration-v4.md) reference guide.
2 changes: 1 addition & 1 deletion docs/articles/configuration-v2.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# HTMX 2.x configuration

HTMX 2.x is the default target. Calling `UseHtmxV2` is optional unless settings need to be changed:
Call `UseHtmxV2` when the application loads HTMX 2.x:

```csharp
using Ramstack.HtmxToolkit;
Expand Down
2 changes: 1 addition & 1 deletion docs/articles/configuration-v4.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# HTMX 4.x configuration

Select V4 explicitly when the application loads HTMX 4.x:
Call `UseHtmxV4` when the application loads HTMX 4.x:

```csharp
using Ramstack.HtmxToolkit;
Expand Down
1 change: 1 addition & 0 deletions docs/articles/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Application-wide settings are registered with `AddHtmxToolkit` and rendered into the page by `<htmx-config />`.
HtmxToolkit uses a different strongly typed configuration class for each supported HTMX major version.
Selecting the version is required; see [Choose an HTMX version](choosing-version.md).

## Configure HTMX

Expand Down
2 changes: 1 addition & 1 deletion docs/articles/forms-and-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ Run the application as in Getting started and open <http://localhost:5080/Profil
5. Disable JavaScript and reload. The form should still save using normal navigation.

For a valid HTMX save, the Network panel shows a POST followed by a GET for the summary.
With the default HTMX 2.x setup, the POST response carries `HX-Trigger-After-Swap`.
With HTMX 2.x, the POST response carries `HX-Trigger-After-Swap`.
HTMX 4.x uses `HX-Trigger` for the same Toolkit call; see [event timing](version-compatibility.md#response-events).

## Why validation uses HTTP 200 here
Expand Down
5 changes: 3 additions & 2 deletions docs/articles/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ using Ramstack.HtmxToolkit.Hosting;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddHtmxToolkit();
builder.Services.AddHtmxToolkit(options => options.UseHtmxV2());

var app = builder.Build();

Expand All @@ -41,7 +41,8 @@ app.MapRazorPages();
app.Run();
```

`AddHtmxToolkit()` selects HTMX 2.x by default. It registers toolkit options; it does not download HTMX.
`UseHtmxV2()` selects the HTMX major version that this tutorial loads. The selection is required: the application
does not start without it. `AddHtmxToolkit` registers toolkit options; it does not download HTMX.
`UseStaticFiles()` serves the Toolkit script included in the package.
See [Static assets and deployment](static-assets.md) for `MapStaticAssets()` and production caching.

Expand Down
22 changes: 15 additions & 7 deletions docs/articles/migrate-to-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ step; see [Version compatibility](version-compatibility.md) for that.

| Area | Change | Detected by |
|---------------------------------------------------------------------|-------------------------------------------------------------------------------------|-------------------|
| [Registration](#register-the-toolkit-and-select-the-htmx-version) | Call `AddHtmxToolkit()` and select the HTMX version; HTMX 2.x is the default | **Silent** |
| [Global configuration](#move-global-configuration-to-code) | `<htmx-config>` attributes move to `AddHtmxToolkit()` | **Silent** |
| [Registration](#register-the-toolkit-and-select-the-htmx-version) | Call `AddHtmxToolkit` and select the HTMX version | Runtime exception |
| [Global configuration](#move-global-configuration-to-code) | `<htmx-config>` attributes move to `AddHtmxToolkit` | **Silent** |
| [Antiforgery](#antiforgery-metadata-is-on-by-default) | Metadata on by default, moved out of `htmx.config`; token sent to own origin only | **Silent** |
| [Toolkit script](#load-the-toolkit-script-as-a-static-asset) | Inline script helpers and `MapHtmxAntiforgeryScript` replaced by a static web asset | Build, **silent** |
| [URL Tag Helper](#rename-the-url-tag-helper-attributes) | `hx-*` URL attributes become `asp-*`; `hx-host` and `hx-protocol` are removed | **Silent** |
Expand Down Expand Up @@ -49,21 +49,25 @@ Add these namespaces where needed:

## Register the toolkit and select the HTMX version

Version 1.x had no registration and was written for HTMX 1.9.x. Version 2.0 registers its options with `AddHtmxToolkit()`
and generates markup for the selected HTMX version. **The default is HTMX 2.x.**
Version 1.x had no registration and was written for HTMX 1.9.x. Version 2.0 registers its options with `AddHtmxToolkit`
and generates markup for the selected HTMX version. **Selecting a version is required.**

If the application still loads HTMX 1.9.x, select it explicitly:
If the application still loads HTMX 1.9.x, select it:

```csharp
using Ramstack.HtmxToolkit.Hosting;

builder.Services.AddHtmxToolkit(options => options.UseHtmxV1());
```

For HTMX 2.x, `builder.Services.AddHtmxToolkit()` is enough. The selected version controls the configuration
For HTMX 2.x or 4.x, call `UseHtmxV2` or `UseHtmxV4` instead. The selected version controls the configuration
properties available in code and the attributes generated by the Tag Helpers.
See [Choose an HTMX version](choosing-version.md).

If `AddHtmxToolkit` selects no version, the application fails to start. If `AddHtmxToolkit` is not called,
the application starts, but the Tag Helpers that depend on the version, such as `<htmx-config />`, and `TriggerEvent`
throw `InvalidOperationException` when they run.

## Move global configuration to code

In 1.x, `<meta htmx-config>` and `<htmx-config>` accepted HTMX settings as attributes.
Expand Down Expand Up @@ -134,7 +138,11 @@ or [HTMX 4.x](configuration-v4.md) configuration.
> disable the metadata:
>
> ```csharp
> builder.Services.AddHtmxToolkit(options => options.IncludeAntiforgeryToken = false);
> builder.Services.AddHtmxToolkit(options =>
> {
> options.IncludeAntiforgeryToken = false;
> options.UseHtmxV2();
> });
> ```

The metadata also moved. In 1.x it was an `antiForgery` object inside the configuration JSON, and the script
Expand Down
2 changes: 1 addition & 1 deletion docs/articles/minimal-apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ using Ramstack.HtmxToolkit;
using Ramstack.HtmxToolkit.Hosting;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHtmxToolkit();
builder.Services.AddHtmxToolkit(options => options.UseHtmxV2());
builder.Services.AddAntiforgery(options => options.HeaderName = "RequestVerificationToken");

var app = builder.Build();
Expand Down
6 changes: 3 additions & 3 deletions docs/articles/mvc.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Use HtmxToolkit with MVC

This example uses ASP.NET Core MVC on .NET 10 and the default HTMX 2.x target.
This example uses ASP.NET Core MVC on .NET 10 and HTMX 2.x.
It loads product details into a partial view, then edits the product name with server validation.
The request and response APIs are the same as in Razor Pages;
MVC uses controller actions and `PartialView()` instead of page handlers and `Partial()`.
Expand All @@ -26,7 +26,7 @@ builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
builder.Services.AddHtmxToolkit();
builder.Services.AddHtmxToolkit(options => options.UseHtmxV2());
builder.Services.AddSingleton<ProductStore>();

var app = builder.Build();
Expand All @@ -38,7 +38,7 @@ app.Run();
```

The antiforgery filter validates the POST action used below.
`AddHtmxToolkit()` does not install that filter for you.
`AddHtmxToolkit` does not install that filter for you.

Add these lines to the existing `Views/_ViewImports.cshtml`:

Expand Down
6 changes: 3 additions & 3 deletions docs/articles/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,14 @@ registration, and layout needed to run it.
| Application feature | Setup |
|-------------------------------------------------------------------|--------------------------------------------------------------------------------------|
| Read request headers or set ordinary response headers | Reference the package and import `Ramstack.HtmxToolkit` in C# |
| Choose a target version or configure HTMX | Call `AddHtmxToolkit()`; render `<htmx-config />` in Razor for browser configuration |
| Trigger client events, use Tag Helpers, or configure HTMX | Select a version in `AddHtmxToolkit`; render `<htmx-config />` in Razor |
| Generate URLs or JSON attributes in Razor | Register the toolkit Tag Helpers in `_ViewImports.cshtml` |
| Attach antiforgery tokens automatically | Render configuration metadata, load the Toolkit script, and enable server validation |
| Use compatibility morph swaps or replay duplicate event names | Load the Toolkit script; follow the relevant feature's setup |
| Tell full-page and fragment requests apart with HTMX 1.9.x or 2.x | Load the Toolkit script |

For HTMX 4.x, register the target version even if you only use response events, so the toolkit chooses the
correct trigger headers. In a pure Minimal API application, HTML rendering and token metadata are supplied
Select the version even if you only use response events, so the toolkit chooses the correct trigger headers.
In a pure Minimal API application, HTML rendering and token metadata are supplied
by the application; see the [Minimal API example](minimal-apis.md).

## Choose the integration style
Expand Down
2 changes: 1 addition & 1 deletion docs/articles/samples.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dotnet run --project samples/Ramstack.HtmxToolkit.Demo --no-launch-profile -- --
Open <http://localhost:5000>. This command uses HTTP for local development, so a development HTTPS certificate
is not needed. The demo loads HTMX from a CDN and requires a network connection for that script.

The demo currently selects HTMX 4.x. The introductory tutorials use HTMX 2.x, the toolkit default.
The demo currently selects HTMX 4.x. The introductory tutorials use HTMX 2.x.
To try another supported version, change both `UseHtmxV4(...)` in `Program.cs` and the HTMX script in
`Pages/Shared/_Layout.cshtml`. Review [Version compatibility](version-compatibility.md) before changing
version-specific options such as `NoSwap`.
Expand Down
13 changes: 11 additions & 2 deletions docs/articles/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,22 @@ Check the response status. HTMX cannot process `HX-Redirect` or other `HX-*` hea
because the browser follows that redirect internally. Return status 200 with `HX-Redirect` for the HTMX request,
and use an ordinary ASP.NET Core redirect only for the non-HTMX path.

## The HTMX version is not selected

**Symptom:** The application fails to start with `OptionsValidationException`, or a page or a `TriggerEvent` call
throws `InvalidOperationException`, with the message `The HTMX version is not selected`.

**Check:** Call `AddHtmxToolkit` in `Program.cs` and select the major version that the browser loads:

```csharp
builder.Services.AddHtmxToolkit(options => options.UseHtmxV2());
```

## The wrong per-request attribute is generated

HTMX 1.9.x and 2.x receive `hx-request`; HTMX 4.x receives `hx-config`. Check `UseHtmxV1`, `UseHtmxV2`,
or `UseHtmxV4` in service registration and verify that the browser loads the same major version.

If no version is selected, HtmxToolkit uses V2.

## Request metadata is null

Some properties are version-specific:
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Choose your starting point:
## Supported environments

The package targets .NET 6 and runs on .NET 6 or later.
It supports HTMX 1.9.x, HTMX 2.x, and HTMX 4.x; HTMX 2.x is the default target.
It supports HTMX 1.9.x, HTMX 2.x, and HTMX 4.x; the application selects the version it loads.

> [!IMPORTANT]
> HtmxToolkit does not bundle HTMX. Add the matching HTMX client release to the application separately.
4 changes: 2 additions & 2 deletions docs/snippets/configuration/RegisterToolkit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

builder.Services.AddRazorPages();

// HTMX 2.x is the default. Select the version that your pages load,
// and set only the HTMX options that should differ from the HTMX defaults.
// Select the version that your pages load, and set only
// the HTMX options that should differ from the HTMX defaults.
builder.Services.AddHtmxToolkit(options =>
{
options.UseHtmxV4(config =>
Expand Down
42 changes: 32 additions & 10 deletions src/Ramstack.HtmxToolkit/Configuration/HtmxToolkitOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,37 @@ namespace Ramstack.HtmxToolkit.Configuration;
/// </summary>
public sealed class HtmxToolkitOptions
{
/// <summary>
/// The message reported when no HTMX version has been selected.
/// </summary>
internal const string VersionNotSelectedMessage =
"The HTMX version is not selected. Call UseHtmxV1, UseHtmxV2, or UseHtmxV4 in AddHtmxToolkit.";

private HtmxConfig? _config;

/// <summary>
/// Gets the configuration for the selected HTMX major version.
/// </summary>
/// <remarks>
/// If no version has been selected, reading this property selects HTMX 2.x.
/// A later call to <see cref="UseHtmxV1" /> or <see cref="UseHtmxV4" /> then throws
/// <see cref="InvalidOperationException" />. Reading <see cref="TargetVersion" />
/// or calling <see cref="GetHtmxConfig{TConfig}" /> has the same effect.
/// Throws <see cref="InvalidOperationException" /> if no version has been selected with
/// <see cref="UseHtmxV1" />, <see cref="UseHtmxV2" />, or <see cref="UseHtmxV4" />.
/// </remarks>
public HtmxConfig HtmxConfig
{
get
{
if (_config is null)
UseHtmxV2();
Error_VersionNotSelected();

return _config;
}
}

/// <summary>
/// Gets a value indicating whether an HTMX version has been selected.
/// </summary>
internal bool IsVersionSelected => _config is not null;

/// <summary>
/// Gets or sets a value indicating whether antiforgery request metadata is rendered
/// by the configuration tag helper.
Expand All @@ -41,8 +50,14 @@ public HtmxConfig HtmxConfig
/// Gets the HTMX major version used for version-sensitive generated markup and response headers.
/// </summary>
/// <remarks>
/// For example, with HTMX 4.x, <see cref="HtmxResponse.TriggerEvent(string, HtmxTriggerTiming)" />
/// emits events with any <see cref="HtmxTriggerTiming" /> value through the <c>HX-Trigger</c> header.
/// <para>
/// For example, with HTMX 4.x, <see cref="HtmxResponse.TriggerEvent(string, HtmxTriggerTiming)" />
/// emits events with any <see cref="HtmxTriggerTiming" /> value through the <c>HX-Trigger</c> header.
/// </para>
/// <para>
/// Throws <see cref="InvalidOperationException" /> if no version has been selected,
/// as when reading <see cref="HtmxConfig" />.
/// </para>
/// </remarks>
public HtmxTargetVersion TargetVersion => HtmxConfig.TargetVersion;

Expand Down Expand Up @@ -99,9 +114,8 @@ public HtmxToolkitOptions UseHtmxV4(Action<HtmxV4Config>? configure = null)
/// The requested configuration instance.
/// </returns>
/// <remarks>
/// If no version has been selected, HTMX 2.x is selected, as when reading <see cref="HtmxConfig" />.
/// Throws <see cref="InvalidOperationException" /> if the configuration of the selected version
/// is not a <typeparamref name="TConfig" />.
/// Throws <see cref="InvalidOperationException" /> if no version has been selected,
/// or if the configuration of the selected version is not a <typeparamref name="TConfig" />.
/// </remarks>
public TConfig GetHtmxConfig<TConfig>() where TConfig : HtmxConfig
{
Expand Down Expand Up @@ -148,6 +162,14 @@ private void EnsureCanSelectVersion(HtmxTargetVersion version)
Error_ReconfigureTargetVersion(current, version);
}

/// <summary>
/// Throws an <see cref="InvalidOperationException" /> when no HTMX version has been selected.
/// </summary>
/// <exception cref="InvalidOperationException">Always thrown.</exception>
[DoesNotReturn]
private static void Error_VersionNotSelected() =>
throw new InvalidOperationException(VersionNotSelectedMessage);

/// <summary>
/// Throws an <see cref="InvalidOperationException" /> when the requested HTMX
/// configuration type does not match the configured target version.
Expand Down
Loading
Loading