diff --git a/README.md b/README.md index 204ba74..7c02d17 100644 --- a/README.md +++ b/README.md @@ -305,6 +305,7 @@ Then open . The [guides](https://rameel.github.io/ramstack.htmxtoolkit/articles/index.html) cover everything from a first tutorial to recipes and troubleshooting. The [API reference](https://rameel.github.io/ramstack.htmxtoolkit/api/) describes every public type and member. +Upgrading from 1.x? See [Migrate from 1.x to 2.0](https://rameel.github.io/ramstack.htmxtoolkit/articles/migrate-to-v2.html). ## Contributing diff --git a/docs/articles/index.md b/docs/articles/index.md index 6ee2311..4069409 100644 --- a/docs/articles/index.md +++ b/docs/articles/index.md @@ -60,6 +60,7 @@ For an existing integration, browse the task guides below or [run the demo](samp Static assets and deploymentLoad the Toolkit script, configure caching, and publish the application. Common recipesImplement validation, polling, redirects, and cross-component updates. TroubleshootingDiagnose failures from generated markup and browser network data. + Migrate from 1.x to 2.0Update registration, configuration, Tag Helpers, and response code for 2.0. > [!TIP] diff --git a/docs/articles/migrate-to-v2.md b/docs/articles/migrate-to-v2.md new file mode 100644 index 0000000..0709eec --- /dev/null +++ b/docs/articles/migrate-to-v2.md @@ -0,0 +1,476 @@ +# Migrate from 1.x to 2.0 + +HtmxToolkit 2.0 adds HTMX 2.x and 4.x support next to HTMX 1.9.x. Supporting three major versions meant +redesigning the parts of 1.x that assumed one: global configuration became version-specific, the Toolkit script +became a static web asset that all versions use, and several response APIs were renamed or removed +because they could not behave the same way across versions. + +Most of the changes show up as build errors. Some do not: the old Razor attributes are rendered unchanged, +and some runtime behavior changed. These changes are marked as **silent** below. Check them even when +the application builds. + +This guide covers upgrading the library. Moving the application to another HTMX major version is a separate +step; see [Version compatibility](version-compatibility.md) for that. + +## Summary + +| 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) | `` attributes move to `AddHtmxToolkit()` | **Silent** | +| [Antiforgery](#antiforgery-metadata-is-on-by-default) | Metadata is included by default and no longer stored in `htmx.config` | **Silent** | +| [Toolkit script](#load-the-toolkit-script-as-a-static-asset) | Inline script helpers and `MapHtmxAntiforgeryScript` replaced by a static web asset | Build | +| [URL Tag Helper](#rename-the-url-tag-helper-attributes) | `hx-controller`, `hx-action`, `hx-route-*`, and similar become `asp-*` | **Silent** | +| [Requests](#update-request-handling) | `IsHtmxRequest()` is `false` for history restoration; `Boosted` filter renamed | Build, **silent** | +| [Action result wrappers](#configure-responses-through-responsehtmx) | `IActionResult.Htmx(...)` removed | Build | +| [Polling](#replace-stoppolling) | `StopPolling` removed | Build | +| [History](#update-history-calls) | `PreventPushUrl` and `PreventReplaceUrl` become `PreventHistoryUpdate` | Build | +| [HX-Location](#replace-ajaxcontext-with-htmxlocationoptions) | `AjaxContext` becomes `HtmxLocationOptions` | Build | +| [Client events](#update-client-events) | Event names are no longer camel-cased; repeated names are kept | Build, **silent** | +| [Typed response headers](#update-typed-response-headers) | `HtmxResponseHeaders` is a struct; event properties are read-only | Build | +| [`[HtmxResponse]`](#update-htmxresponse) | Restricted to classes and methods, one per target, inherited | Build, **silent** | + +## Update the package + +```console +dotnet add package Ramstack.HtmxToolkit --version 2.0.0 +``` + +The package still targets .NET 6 and runs on .NET 6 or later. + +Add these namespaces where needed: + +| Namespace | Contains | +|--------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Ramstack.HtmxToolkit.Hosting` | `AddHtmxToolkit` | +| `Ramstack.HtmxToolkit.Configuration` | `HtmxToolkitOptions`, `HtmxV1Config`, `HtmxV2Config`, `HtmxV4Config`, and configuration enums such as `HtmxScrollBehavior`, `HtmxBinaryType`, and `HttpVerb` | + +`HtmxScrollBehavior` and `HtmxBinaryType` moved from `Ramstack.HtmxToolkit` to `Ramstack.HtmxToolkit.Configuration`. + +## 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.** + +If the application still loads HTMX 1.9.x, select it explicitly: + +```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 +properties available in code and the attributes generated by the Tag Helpers. +See [Choose an HTMX version](choosing-version.md). + +## Move global configuration to code + +In 1.x, `` and `` accepted HTMX settings as attributes. +In 2.0, the settings are strongly typed properties of the version-specific configuration, and +the Tag Helper takes no settings. + +1.x: + +```razor + +``` + +2.0: + +```csharp +using Ramstack.HtmxToolkit; +using Ramstack.HtmxToolkit.Configuration; +using Ramstack.HtmxToolkit.Hosting; + +builder.Services.AddHtmxToolkit(options => +{ + options.UseHtmxV2(config => + { + config.DefaultSwapStyle = HtmxSwap.OuterHtml; + config.ScrollBehavior = HtmxScrollBehavior.Smooth; + config.HistoryCacheSize = 20; + }); +}); +``` + +```razor + + + +``` + +> [!WARNING] +> The old attributes do not cause build errors. The Tag Helper no longer binds them, so Razor renders them +> unchanged on the `` element and HTMX ignores them. Search the layouts for `htmx-config` and move every +> attribute to code. + +Each attribute maps to the property with the same name in PascalCase, such as `history-cache-size` to `HistoryCacheSize`, +on `HtmxV1Config` or `HtmxV2Config`. The exceptions are: + +| 1.x attribute | 2.0 | +|-------------------------------|--------------------------------------------------------------------------------------------------------------------| +| `include-antiforgery-token` | `HtmxToolkitOptions.IncludeAntiforgeryToken`, now `true` by default; see the next section | +| `use-template-fragments` | `HtmxV1Config.UseTemplateFragments` only; HTMX 2.x always parses responses with templates | +| `trigger-specs-cache` | `TriggerSpecsCacheEnabled` (`bool?`) | +| `methods-that-use-url-params` | `MethodsThatUseUrlParams` (`HttpVerb[]` instead of `string[]`) | +| `ws-reconnect-delay` | Removed: HTMX accepts only a function or the default `"full-jitter"`, and a function cannot be passed through JSON | + +For every property, see [HTMX 1.9.x](configuration-v1.md), [HTMX 2.x](configuration-v2.md), +or [HTMX 4.x](configuration-v4.md) configuration. + +### Antiforgery metadata is on by default + +`` now includes antiforgery metadata unless you opt out. In 1.x, it did so only with +`include-antiforgery-token="true"`. + +> [!WARNING] +> To include the metadata, ASP.NET Core generates antiforgery tokens for every page that renders ``. +> The response then sets the antiforgery cookie, `Cache-Control: no-cache, no-store`, and `X-Frame-Options: SAMEORIGIN`. +> If such a page was publicly cacheable in 1.x and the application does not send HTMX requests that need a token, +> disable the metadata: +> +> ```csharp +> builder.Services.AddHtmxToolkit(options => options.IncludeAntiforgeryToken = false); +> ``` + +The metadata also moved. In 1.x it was an `antiForgery` object inside the configuration JSON, and the script +copied it to `htmx.config.antiForgery`. In 2.0 it is written as data attributes, and `htmx.config.antiForgery` +is no longer set: + +```html + +``` + +The Toolkit script reads these attributes itself. Update only custom JavaScript that read `htmx.config.antiForgery`: + +```js +const { antiforgeryRequestToken, antiforgeryHeaderName } = + document.querySelector("meta[name='htmx-config']").dataset; +``` + +See [Antiforgery and Toolkit script](antiforgery.md). + +## Load the Toolkit script as a static asset + +These 1.x APIs were removed: + +| 1.x | 2.0 | +|-----------------------------------------------|-------------------------------------------------------| +| `@Html.HtmxAntiforgeryScript()` inline script | Static web asset loaded with ` + +``` + +2.0: + +```razor + + +``` + +Remove the `MapHtmxAntiforgeryScript()` call and make sure the application serves static files: + +```csharp +app.UseStaticFiles(); +``` + +On ASP.NET Core 9 or later, you can use `app.MapStaticAssets()` with `.WithStaticAssets()` instead. +`asp-append-version` and `~/` require the standard Tag Helpers: +`@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers`. + +Because the script is no longer inline, a Content Security Policy no longer needs `'unsafe-inline'` or a nonce for it. + +Load the script on every page that uses HTMX, even when the application does not use automatic antiforgery. +In 2.0 it also sends `HX-Request-Type` for HTMX 1.9.x and 2.x, replays events whose name repeats, +and provides the `ramstack-swap` extension. See [What the Toolkit script does](antiforgery.md#what-the-toolkit-script-does) +and [Static assets and deployment](static-assets.md). + +## Rename the URL Tag Helper attributes + +The URL Tag Helper now reads the standard ASP.NET Core `asp-*` routing attributes instead of its own `hx-*` copies. +HTMX 4.x defines a native `hx-action` attribute, which made the old naming conflict with HTMX itself. + +| 1.x | 2.0 | +|---------------------|----------------------| +| `hx-controller` | `asp-controller` | +| `hx-action` | `asp-action` | +| `hx-area` | `asp-area` | +| `hx-page` | `asp-page` | +| `hx-page-handler` | `asp-page-handler` | +| `hx-route` | `asp-route` | +| `hx-route-*` | `asp-route-*` | +| `hx-all-route-data` | `asp-all-route-data` | +| `hx-host` | `asp-host` | +| `hx-protocol` | `asp-protocol` | +| `hx-fragment` | `asp-fragment` | + +The HTMX request attribute is now required and must be written without a value. In 1.x, `hx-get` was used +when no request attribute was present. + +1.x: + +```razor + + + +``` + +2.0: + +```razor + + + +``` + +> [!WARNING] +> Old attributes do not cause build errors. Razor renders them unchanged, and HTMX 1.9.x and 2.x ignore them, +> so the element sends no request. HTMX 4.x reads a leftover `hx-action="Detail"` as the request URL `Detail`. + +Find the old attributes from the project directory. On Linux and macOS: + +```console +grep -rniE '\bhx-(controller|action|area|page|route|all-route-data|host|protocol|fragment)\b' --include='*.cshtml' . +``` + +In PowerShell: + +```powershell +Get-ChildItem -Recurse -Filter *.cshtml | + Select-String -Pattern '\bhx-(controller|action|area|page|route|all-route-data|host|protocol|fragment)\b' +``` + +In HTMX 4.x markup, `hx-action` can be legitimate; review those matches. + +Check the following while migrating: + +- **Route values are strings.** In 1.x, `hx-route-*` values were typed as `object`, so Razor compiled them as C# code. + `asp-route-*` values are strings, as in the built-in Tag Helpers: `hx-route-id="Model.Id"` must become + `asp-route-id="@Model.Id"`. Without `@`, the value is the literal text `Model.Id`. +- **`asp-all-route-data` takes `IDictionary`**, like the built-in Tag Helpers, + instead of `RouteValueDictionary`. Convert other values to strings. +- **Built-in Tag Helpers process the same attributes** on ``, `
`, `