Building Your First App with HTMX and .NET - Part I
By FoxLearn 2/28/2025 2:56:03 AM 199
This makes it incredibly easy to build interactive and modern user interfaces without the need for heavyweight frameworks like React or Angular.
HTMX’s appeal lies in its simplicity and lightweight nature, which has made it an attractive option for developers looking to enhance their web applications. In this post, we will explore HTMX in combination with .NET 8 to build a basic blog application, adding articles, editing them, and displaying them interactively.
Creating database
USE master; GO CREATE DATABASE BlogApp GO USE BlogApp; GO CREATE TABLE dbo.[Articles] ( [ArticleId] UNIQUEIDENTIFIER NOT NULL, [Title] nvarchar(200) NOT NULL, [Content] nvarchar(max) NOT NULL, [CreatedAt] datetimeoffset NOT NULL, [IsPublished] bit NOT NULL, CONSTRAINT [PK_Articles] PRIMARY KEY ([ArticleId]) ); GO
Solution Setup
Now, let’s create a new .NET solution:
dotnet new web -o BlogApp dotnet new sln -n BlogApp dotnet sln add --in-root BlogApp dotnet add BlogApp package Microsoft.EntityFrameworkCore.SqlServer
We will now open the solution and set up our first Razor component. Create a MainPage.razor
file with this content:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" /> </head> <body class="mb-5"> <header class="navbar navbar-expand bg-white fixed-top border-bottom px-4 z-2"> <div class="container-fluid"> <div class="collapse navbar-collapse"> <ul class="navbar-nav ms-auto"> <li class="nav-item"> <span>Welcome to BlogApp!</span> </li> </ul> </div> </div> </header> <aside class="navbar navbar-expand z-3 position-fixed top-0 start-0 bottom-0 border-end bg-white p-0 ms-0" style="width: 16.25rem;"> <a class="navbar-brand py-0 px-4 d-flex align-items-center" href="#"> <img class="d-block" src="https://placehold.co/100x50" alt="Logo"> </a> <div class="overflow-y-auto" style="height: calc(100% - 3.875rem);"> <div class="d-flex flex-column px-4"> <span class="mt-4 px-3 py-2 fw-bold fs-6">MENU</span> <ul class="nav nav-pills"> <li class="nav-item"> <a class="nav-link link-dark" href="#"> <span>All Articles</span> </a> </li> </ul> </div> </div> </aside> <main style="padding-left: 16.25rem; padding-top:3.875rem"> <div class="container-fluid p-4"> <!-- Dynamic content will load here --> </div> </main> <script src="https://unpkg.com/[email protected]"></script> </body> </html>
In this layout, we have a header, sidebar, and a main content area. Next, we update the Program.cs
file to configure the app:
using Microsoft.AspNetCore.Http.HttpResults; using BlogApp; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents(); builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); var app = builder.Build(); app.MapGet("/", () => { return new RazorComponentResult<MainPage>(); }); app.Run();
Entity Framework Setup
Now, let’s configure Entity Framework by creating BlogAppDbContext.cs
:
using Microsoft.EntityFrameworkCore; namespace BlogApp; public class BlogAppDbContext : DbContext { public BlogAppDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(modelBuilder); } }
AppSettings Configuration
Edit the appsettings.json
file to set up the connection string:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionString": "Server=localhost,1433;Database=BlogApp;User ID=sa;Password=Sqlserver123$;MultipleActiveResultSets=true;TrustServerCertificate=True;" }
Then, in Program.cs
, add:
builder.Services.AddDbContext<BlogAppDbContext>(options => options.UseSqlServer(builder.Configuration["ConnectionString"]));
The Model
Create an Article.cs
file:
namespace BlogApp.Articles; public class Article { public Guid ArticleId { get; set; } public string Title { get; set; } = default!; public string Content { get; set; } = default!; public DateTimeOffset CreatedAt { get; set; } public bool IsPublished { get; set; } }
List Articles with HTMX
Create a file called ListArticles.cs
to define the action of listing articles:
using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace BlogApp.Articles; public static class ListArticles { public class Request { public string? Title { get; set; } } public static async Task<RazorComponentResult> HandlePage([FromServices] BlogAppDbContext dbContext, [AsParameters] Request request) { var title = request.Title ?? string.Empty; var results = await dbContext.Set<Article>() .AsNoTracking() .Where(a => a.Title.Contains(title)) .ToListAsync(); return new RazorComponentResult<ListArticlesPage>(new { Results = results }); } }
Adding New Articles
Create a RegisterArticle.cs
file to handle new article submissions:
namespace BlogApp.Articles; public static class RegisterArticle { public class Request { public string Title { get; set; } = default!; public string Content { get; set; } = default!; } public static Task<RazorComponentResult> HandlePage() { return Task.FromResult(new RazorComponentResult<RegisterArticlePage>()); } public static async Task<RazorComponentResult> HandleAction([FromServices] BlogAppDbContext dbContext, [FromBody] Request request) { var article = new Article { ArticleId = Guid.NewGuid(), Title = request.Title, Content = request.Content, CreatedAt = DateTimeOffset.UtcNow, IsPublished = false }; dbContext.Set<Article>().Add(article); await dbContext.SaveChangesAsync(); return await ListArticles.HandlePage(dbContext, new ListArticles.Request()); } }
Add HTMX to Register New Articles
Update the main menu in MainPage.razor
with a link to register new articles:
<li class="nav-item"> <a class="nav-link link-dark" href="#" hx-get="/articles/register" hx-target="#main" hx-swap="innerHTML"> <span>Register New Article</span> </a> </li>
Creating the Article Registration Page
Create a RegisterArticlePage.razor
file to define the form for creating new articles:
<h4>Register New Article</h4> <form hx-post="/articles/register" hx-target="#main" hx-swap="innerHTML"> <div class="mb-3"> <label for="Title" class="form-label">Title</label> <input type="text" class="form-control" id="Title" name="Title" required /> </div> <div class="mb-3"> <label for="Content" class="form-label">Content</label> <textarea class="form-control" id="Content" name="Content" rows="5" required></textarea> </div> <button type="submit" class="btn btn-primary">Save</button> </form>
This example shows how you can use HTMX and Razor components in .NET to create a dynamic blog platform with features like creating and listing articles interactively.
By using HTMX attributes like hx-get
, hx-post
, and hx-target
, you can easily manage AJAX-based interactions and build user-friendly applications with minimal effort.