Building Your First App with HTMX and .NET - Part I

By FoxLearn 2/28/2025 2:56:03 AM   199
HTMX offers an elegant way to integrate AJAX, CSS transitions, WebSockets, and Server Sent Events directly into your HTML using simple attributes.

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.