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.