.NET 8: Working with HTMX and Razor Components

.NET 8: Working with HTMX and Razor Components

Razor components are files written in Razor syntax, a templating language that combines HTML and C# code to generate HTML. It serves the same purpose for .NET as JSX does for React.

The introduction of the RazorComponentResult type in .NET 8 has the potential to take our HTMX development to the next level. In our previous post, Getting Started with HTMX: A Beginner's Guide, we used the HtmlContentBuilder class to generate HTML. Now, with the RazorComponentResult class, we can harness the power of Razor components to achieve the same goal.

Let's begin migrating our project here to use Razor components. Create the TodoItemComponent.razor file to render a single Todo item using the following content:

<div>
    <p>@Model.Name</p>
    @if (Model.IsCompleted)
    {
        <input type="checkbox" checked hx-post="/todos/@Model.Id/toggle" hx-target="closest div" hx-swap="outerHTML" />
    }
    else
    {
        <input type="checkbox" hx-post="/todos/@Model.Id/toggle" hx-target="closest div" hx-swap="outerHTML" />
    }
    <button hx-delete="/todos/@Model.Id" hx-target="closest div" hx-swap="outerHTML">X</button>
</div>
@code {
    [Parameter]
    public TodoItem Model { get; set; } = new();
}

The TodoFormComponent.razor file will contain the Form for adding a new Todo item:

<form hx-post="/todos" hx-swap="beforebegin" hx-ext="json-enc">
    <input type="text" name="name" />
    <button type="submit">Add</button>
</form>
@code {

}

The TodoItemListComponent.razor file will use the previous components:

<div>
    @foreach (var item in Model)
    {
        <TodoItemComponent Model="@item"></TodoItemComponent>
    }
    <TodoFormComponent></TodoFormComponent>
</div>
@code {
    [Parameter]
    public List<TodoItem> Model { get; set; } = new();
}

And finally, the root component for our application is the DocumentComponent.razor file:

<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width =device-width, initial-scale=1.0">
    <script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
</head>
<body hx-get="/todos" hx-trigger="load" hx-swap="innerHTML">
</body>
</html>
@code {

}

The final Program.cs file will be as follows:

using Microsoft.AspNetCore.Http.HttpResults;
using TodoApi;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents();
var app = builder.Build();
var db = new List<TodoItem>()
{
    new TodoItem() { Id = Guid.NewGuid(), Name = "abc", IsCompleted = true },
    new TodoItem() { Id = Guid.NewGuid(), Name = "123", IsCompleted = false },
};
app.MapGet("/", () =>
{
    return new RazorComponentResult<DocumentComponent>();
});
app.MapGet("/todos", () => new RazorComponentResult<TodoItemListComponent>(new { Model = db }));
app.MapPost("/todos/{id}/toggle", (string id) =>
{
    var todo = db.First(t => t.Id.ToString() == id);
    todo.IsCompleted = !todo.IsCompleted;
    return new RazorComponentResult<TodoItemComponent>(new { Model = todo });
});
app.MapDelete("/todos/{id}", (string id) =>
{
    var todo = db.First(t => t.Id.ToString() == id);
    db.Remove(todo);
});
app.MapPost("/todos", (AddTodoItem command) =>
{
    var todo = new TodoItem { Id = Guid.NewGuid(), Name = command.Name, IsCompleted = false };
    db.Add(todo);
    return new RazorComponentResult<TodoItemComponent>(new { Model = todo });
});
app.Run();

Razor components allow us to embed C# code in HTML in a clean, readable way, making it easier to design, reuse, and maintain HTML. As a result, implementing our HTMX endpoint becomes much simpler, giving the impression that .NET 8 was designed with HTMX in mind (In fact, it was the support for server-side rendering that made this possible). You can find the final code here. Thank you, and happy coding.