Architecture Overview

Introduction

Rizzy is designed to bridge the gap between traditional server-side ASP.NET Core applications (MVC or Minimal APIs) and modern dynamic user interfaces powered by HTMX. It achieves this by leveraging Blazor’s component model for server-side rendering (SSR) of HTML fragments, which are then delivered to the client for HTMX to handle.

This document provides a high-level overview of Rizzy’s architecture and the flow of requests within a Rizzy-enabled application.

Core Philosophy

The central idea behind Rizzy is to:

  1. Use Blazor Components for UI: Define UI structures and logic using familiar .razor components.
  2. Render Server-Side: Utilize Blazor’s efficient server-side rendering capabilities (HtmlRenderer) to generate HTML.
  3. Enhance with HTMX: Use HTMX attributes on the client-side to trigger requests for partial page updates.
  4. Return HTML Fragments: Have server endpoints return only the necessary HTML fragments generated by Blazor components, rather than full pages or JSON data.
  5. Leverage HTMX for Swapping: Let HTMX handle swapping the received HTML fragments into the correct places in the DOM.

This approach allows developers to benefit from Blazor’s component model without the complexity of Blazor Server’s SignalR connection or Blazor WebAssembly’s client-side runtime for every interaction, reserving full interactivity for specific components where needed (often augmented by libraries like Alpine.js).

Request Flow

Understanding the request lifecycle is key to understanding Rizzy. There are two primary flows: Initial Page Load and HTMX Partial Update.

Initial Page Load (Standard Request)

  1. Browser Request: The user navigates to a URL, sending a standard HTTP GET request.
  2. ASP.NET Core Pipeline: The request goes through routing and standard middleware.
  3. Controller/Endpoint Execution: The request reaches an MVC Controller action or a Minimal API endpoint.
  4. Rizzy Service Call: The action/endpoint typically calls rizzyService.View<TComponent>(...).
  5. RzPage Component: Rizzy prepares parameters for the RzPage component, including the target component (TComponent) and any data.
  6. Layout Selection: RzPage determines the appropriate layout:
    • It checks for a @layout directive on TComponent.
    • If none, it uses the DefaultLayout configured via RizzyConfig.
    • The layout is likely wrapped in HtmxApp<TLayout> and HtmxLayout<TLayout>. Since this is not an HTMX request, HtmxLayout renders the full specified layout (TLayout).
  7. Blazor SSR: The Blazor HtmlRenderer processes the component tree (RzPage -> HtmxApp -> HtmxLayout -> YourLayout -> TComponent).
  8. Full HTML Response: The renderer generates the complete HTML for the page.
  9. Browser Renders: The browser receives and renders the full HTML page. HTMX attributes (hx-get, hx-post, etc.) are now present in the DOM, ready for user interaction.

HTMX Partial Update Request

  1. User Interaction: The user interacts with an element containing HTMX attributes (e.g., clicks a button with hx-post).
  2. Browser Request (HTMX): The browser, via HTMX, sends an AJAX request (GET, POST, etc.) to the specified URL. Crucially, this request includes HTMX-specific headers (like HX-Request: true, HX-Target, HX-Trigger).
  3. ASP.NET Core Pipeline: The request flows through the pipeline.
    • Rizzy Middleware: May intercept the request (e.g., to add nonce headers if configured).
    • Antiforgery Middleware: Validates antiforgery tokens if applicable.
  4. Controller/Endpoint Execution: The request reaches the designated action/endpoint.
    • [HtmxRequest] attribute might guard the action.
    • InitializeBlazorFormData (via RzController) might run to enable [SupplyParameterFromForm].
  5. Rizzy Service Call: The action often calls rizzyService.PartialView<TComponent>(...) or rizzyService.View<TComponent>(...).
  6. Component Rendering (RzPartial or RzPage):
    • If PartialView was called, RzPartial is typically used. It renders TComponent within an EmptyLayout.
    • If View was called, RzPage is used. HtmxLayout detects the HX-Request header and renders a minimal or empty layout instead of the full application layout.
  7. Blazor SSR: The HtmlRenderer processes the (potentially smaller) component tree.
  8. Response Generation:
    • The renderer generates the HTML fragment for the requested component (TComponent).
    • HTMX Response Headers: The controller/endpoint uses HttpContext.Response.Htmx() (or [HtmxResponseAttribute]) to set specific HTMX response headers (e.g., HX-Trigger, HX-Retarget, HX-Reswap).
    • OOB Swaps: If the HtmxSwapService was used during the request, its content (HtmxSwappable components) is added to the response, marked for Out-of-Band swapping (hx-swap-oob).
  9. Partial HTML Response: The server sends back the HTML fragment and the HTMX response headers.
  10. Browser (HTMX Handling):
    • HTMX receives the response.
    • It processes any OOB swap instructions.
    • It swaps the main HTML fragment into the DOM element specified by the original hx-target (or modified by HX-Retarget), using the specified swap style (or modified by HX-Reswap).
    • It processes other response headers (e.g., triggers client-side events via HX-Trigger, redirects via HX-Redirect/HX-Location, updates browser history via HX-Push-Url/HX-Replace-Url).

Key Architectural Pieces

  • Controllers / Minimal API Endpoints: Handle incoming HTTP requests, perform business logic, and decide which Blazor component(s) to render.
  • IRizzyService: Provides the View<T> and PartialView<T> methods, abstracting the component rendering logic for controllers/endpoints.
    • RzPage / RzPartial: Core Rizzy components responsible for setting up the rendering environment for Blazor components, including layout selection.
  • Blazor SSR (HtmlRenderer): The engine that takes the Blazor component tree and renders it to static HTML on the server.
    • HtmxApp<T> / HtmxLayout<T>: Manage the application’s root layout and intelligently switch between full and minimal rendering based on HTMX request headers.
    • HtmxRequest / HtmxResponse: Classes and extensions providing strongly-typed access to read HTMX request headers and write HTMX response headers.
  • HtmxSwapService / HtmxSwappable: Facilitate server-driven Out-of-Band (OOB) swaps.
  • Rizzy Form Components (RzInput*, etc.): Extend Blazor inputs to integrate with EditContext and potentially generate data-val-* attributes.
    • DataAnnotationsProcessor / [SupplyParameterFromForm]: Bridge MVC validation and model binding concepts with Blazor components (Note: [SupplyParameterFromForm] currently relies on internal reflection).
  • Rizzy Middleware: Optional pipeline component for handling cross-cutting concerns like nonce headers.

Benefits of this Architecture

  • Developer Experience: Use familiar Blazor component syntax for UI.
  • Reduced JavaScript: HTMX handles most dynamic updates declaratively in HTML.
  • Leverages SSR: Fast initial loads and SEO benefits from server-rendered HTML.
  • Component Reusability: Blazor components can be reused across different parts of the application.
  • Integration: Fits naturally within existing ASP.NET Core MVC or Minimal API applications.

Considerations

  • State Management: Components rendered via SSR are typically stateless between requests. Managing state across HTMX interactions requires careful consideration.
  • Performance: Blazor component rendering adds overhead compared to returning raw HTML strings. The impact depends on component complexity.
  • Internal Reflection: Be aware that enabling [SupplyParameterFromForm] currently relies on reflection into internal framework APIs (see Forms documentation for details).

This overview provides a conceptual model of how Rizzy works. Refer to specific documentation sections for details on configuration, components, and advanced features.