Htmxor

repository·main·Indexed 41 days ago

https://github.com/egil/htmxor

Htmxor integrates HTMX requests with Blazor Static SSR components to add interactivity to stateless Blazor SSR applications. It provides server-side response operations like HtmxResponse.Trigger, Reswap, Retarget, and Reselect, and includes a candidate endpoint adapter to intercept Blazor Static SSR POST requests while maintaining parity with standard Blazor form handling, antiforgery protection, and session persistence. Supports .NET 10 and .NET 11.

Tokens
63.2K
Snippets
104
Records
277
Agent score
51%

What's inside Htmxor

  1. Understand the Htmxor v1 feature scope

    main

    Htmxor v1 is designed to supercharge Blazor Static SSR by providing explicit component fragment endpoints.

    Supported Features

    • Runtime: .NET 10 LTS. (Note: Previous beta packages are used for .NET 8 compatibility).
    • HTTP Verbs: GET, POST, PUT, PATCH, and DELETE (with fail-closed validation for unsafe verbs).
    • Blazor Integration: Static SSR full pages, PageTitle updates, and support for route/query parameters, authorization metadata, and antiforgery.
    • Fragment Capabilities: Explicit or generated component fragment endpoints using public ASP.NET Core contracts. Fragments are designed to avoid executing unrelated page branches (data-loading/lifecycle methods).
    • Context: Access to htmx request context and official request/response headers.

    Deferred Features (Not in v1)

    • Full <head> merging (core only supports PageTitle via HeadOutlet).
    • Streaming fragment updates.
    • Native AOT support.
    • Arbitrary Blazor event-callback replay from htmx headers.
    • WebSocket and SSE integration.
  2. How routing works in Htmxor v1

    main

    In Htmxor v1, routing is owned by .razor components rather than external controllers or Minimal API handlers. This allows components to participate in both standard Blazor page loads and direct HTMX requests.

    Reachability Modes

    A component can be configured for three modes of reachability:

    1. Normal-only: Accessible only via standard Blazor page/layout paths.
    2. HTMX-only: Accessible only via HTMX requests. Use the [HtmxRoute] attribute on a component without an @page directive to achieve this.
    3. Both: Accessible via both standard Blazor navigation and direct HTMX requests.

    Route Declaration

    • @page: Supplies the normal Blazor route and, by default, the matching direct HTMX GET route. To opt a page out of direct HTMX reachability, an explicit declaration is required.
    • [HtmxRoute]: Used on components without @page to supply an HTMX-only route.
  3. Security and Authorization requirements

    main

    Htmxor treats HTMX request headers as untrusted request data. HTMX-only routing is a reachability choice and not an authorization boundary.

    When using Htmxor, ensure the following:

    • Endpoint Protection: Every generated or adapted endpoint must retain the effective authorization, antiforgery, host, and rate-limit metadata of the original component route.
    • CSRF/Antiforgery: For protected unsafe requests, Htmxor honors the application's existing ASP.NET Core CSRF or token-antiforgery verdict. It does not introduce separate security-mode hooks or origin policies that override your configured protection.
    • Method Binding: The normalized route, HTTP method, and action identity are bound together to prevent information captured for one action from being used to invoke another action or method.
    • Cache Variation: Full-page and fragment responses must set correct cache variation, accounting for both HTMX and authentication-dependent inputs.
  4. Htmxor v1 Architecture: Compiler-backed Attribute Discovery

    main

    Htmxor v1 uses a multi-phase approach to discover HtmxRouteAttribute declarations in Blazor components. Instead of relying on unreliable text parsing of .razor files, it uses a combination of a path-only generator, a final-compilation analyzer, and a compiled-metadata runtime catalog. This ensures that attributes are discovered based on actual C# compiler semantics rather than raw text scanning.

    Key architectural components:

    • Path-only generator: Handles initial discovery paths.
    • Final-compilation analyzer: Analyzes the component after the compiler has resolved symbols and bindings.
    • Compiled-metadata runtime catalog: A runtime structure that stores the discovered route information.

    This design allows users to use @attribute directives directly in .razor files, including complex scenarios like multiline directives after markup or using component-local constants defined in @code blocks.

  5. Understand the Htmxor implementation model

    main

    Htmxor is a component-routed integration for Blazor Static SSR. Unlike 'thin protocol adapters' (which only provide request/response helpers) or 'rendering adapters' (which connect routing to a fragment selector), Htmxor aims for depth.

    It centralizes the following responsibilities to remove endpoint and branching boilerplate from the consuming application:

    • Component-owned routes: Discovering Blazor component routes directly.
    • Automatic full/fragment selection: Handling same-URL requests for both full pages and HTMX fragments.
    • Rendering control: Managing selected and Out-of-Band (OOB) rendering.
    • Browser bridge: Managing history, response handling, cache variation, and client defaults.
    • Security: Integrating HTMX with Blazor's security model.
  6. Route ownership and reachability in Htmxor

    main

    Htmxor is designed to complement rather than replace the standard Blazor routing model. Route ownership should remain within the Razor component. The following table describes how different component declarations affect reachability for normal requests versus HTMX requests:

    Component declarationNormal requestHTMX requestv1 intent
    @page with default Htmxor participationStock Blazor pageDirect static SSR component/fragmentBoth
    explicit Htmxor route without @pageNot routed by BlazorDirect static SSR component/fragmentHTMX-only
    @page with an explicit Htmxor opt-outStock Blazor pageNo direct Htmxor endpointNormal-only

    By default, existing @page dual reachability should be retained to maintain compatibility.

  7. Understand Htmxor code-metrics enforcement

    main

    Htmxor enforces code quality by enabling CA1502, CA1505, and CA1509 as build errors in every project. The enforcement is driven by specific profiles located in eng/quality/code-metrics.

    Each project must declare exactly one profile. The build will fail if a project:

    • Has a missing profile.
    • Uses an unknown profile.
    • Has a duplicate profile.
    • Attempts to override a profile.

    Profiles are categorized into two types:

    1. Legacy Profiles (legacy-*): These act as 'no-regression envelopes' for existing audited paths. They allow existing technical debt to persist without worsening, but they cannot be used by newly added projects.
    2. Role Profiles (production or tests): These are the mandatory profiles for all new projects.

    Note: Metrics are intended to inform code reviews, not replace design judgment. Reviewers should still reject code that violates abstraction levels or focus, even if it falls below the numeric complexity ceilings.

  8. How HtmxFragment handles redirects via HX-Redirect

    main

    Htmxor provides an adaptation mechanism for standard ASP.NET Core redirects when they are triggered by an HTMX request.

    When a stock Razor component request delegate returns a 302 Found response with an absolute same-origin Location header, Htmxor adapts this for HTMX by:

    1. Changing the status code from 302 to 200 OK.
    2. Moving the destination URL from the Location header to the HX-Redirect header.

    This allows HTMX to perform a client-side redirect instead of a standard browser-level redirect, which is necessary for maintaining the HTMX lifecycle during partial updates. This adaptation occurs at the post-delegate seam and does not require custom controllers or Minimal APIs.

  9. How Htmx request marker classification works

    main

    Htmxor uses strict classification for HX-Request and HX-Request-Type headers to determine if a request should be treated as a direct HTMX representation.

    To trigger HTMX-specific behavior (such as core response-header operations), the HX-Request marker must be exactly one normalized lowercase true (after trimming optional HTTP whitespace like spaces or tabs).

    If the marker is missing, blank, false, malformed, comma-joined, or repeated, Htmxor reverts to RoutingMode.Standard. In this mode, the HX-* context is suppressed, and protected response operations (including Reswap(string)) will be rejected before they can mutate headers, status, or body-control state.

  10. How Htmxor handles HTMX-only GET routes

    main

    Htmxor allows you to declare HTMX-only GET routes on Blazor components that do not use the standard @page directive.

    When a component declares an HTMX-only route:

    1. Direct HTMX Requests: An authorized, direct HTMX GET request receives the request-owned component and renders its output directly, without the standard Blazor application shell (no Router lookup or stock HTML shell).
    2. Normal Browser GET Requests: A standard browser GET request to the same URL will return a 404 Not Found.
    3. Security: ASP.NET Core authorization policies are still enforced. Anonymous direct HTMX requests return 401 Unauthorized.
    4. Metadata Preservation: The component retains its route constraints, authorization policies, and any Host metadata declared on its C# partial type.
  11. Pass child content across render-mode boundaries

    main

    In .NET 11, non-generic RenderFragment child content can cross from Static SSR to interactive render modes (Server, WebAssembly, or Auto). The server renders and captures the fragment, then projects that captured tree into the interactive component.

    Limitations:

    • This does not serialize Htmxor action callbacks.
    • It does not serialize generic RenderFragment<T>.
    • It does not serialize element references or event ownership.
    • It does not convert an HTMX fragment into an interactive Blazor component.