.NET 8: Working with HTMX and Razor Components

Somebody who likes to code
Search for a command to run...

Somebody who likes to code
No comments yet. Be the first to comment.
htmx gives you access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext HTMX is gaining attention for its simplicity and...
Google processes tens of thousands of code changes every day across a codebase of hundreds of millions of lines. The secret isn't magic tooling — it's a documented, principled approach to code review

Amazon Simple Notification Service (SNS) looks straightforward on the surface — publish a message, deliver it to subscribers — but the gap between a topic that "works in dev" and one that is productio

Amazon Simple Queue Service (SQS) is deceptively simple to get started with, but surprisingly easy to misconfigure in ways that cause silent message loss, runaway costs, duplicate processing storms, o

HTTP API is the right default choice for the majority of .NET serverless workloads. At 70% less cost than REST API, lower latency, and native JWT authorization, it covers most production requirements

AWS Lambda has become a cornerstone of modern serverless architectures, but writing a function that "just works" is very different from writing one that is performant, cost-effective, secure, and read

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.