Build a server-side web app with HTMX in C#
By FoxLearn 2/7/2025 7:36:54 AM 108
In this guide, we will develop a simple server-side web application using C# and enhance the user experience with HTMX for dynamic front-end updates without writing any JavaScript.
Create a .NET C# Project
First, ensure you have the .NET CLI installed. It's an easy process. Once installed, use the following command to create a new application:
$ dotnet new mvc -n ProductApp
This creates a new directory called /ProductApp
and sets up the basic project structure. Now, let's run the project in development mode:
/ProductApp $ dotnet run
You can now visit localhost:5000
to see the default homepage.
If you need to adjust the port or allow connections from domains other than localhost, modify the ProductApp/Properties/launchSettings.json
. For example, change the port to 4000
as follows:
"applicationUrl": "http://*:4000"
Model Class
Following the Model-View-Controller (MVC) pattern, we’ll create a model class to represent our product information. Inside a newly created /Models
directory, create a Product.cs
file:
// Models/Product.cs namespace ProductApp.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } } }
This model defines a Product
class with Id
, Name
, and Price
properties. Similar to Java, C# uses get;
and set;
to automatically generate getter and setter methods for each property.
Repository Class
Next, we’ll create a simple repository class to manage the products. In a real-world scenario, this class would interact with a database, but for this example, we’ll use an in-memory list:
// ProductRepository.cs using ProductApp.Models; namespace ProductApp { public class ProductRepository { private static List<Product> _products = new List<Product>() { new Product { Id = 1, Name = "Laptop", Price = 999.99m }, new Product { Id = 2, Name = "Smartphone", Price = 599.99m } }; public List<Product> GetAll() { return _products; } public void Add(Product product) { product.Id = _products.Any() ? _products.Max(p => p.Id) + 1 : 1; _products.Add(product); } } }
Here, we have a static list of products and two methods: GetAll()
and Add()
.
The GetAll()
method returns the list of products, while the Add()
method adds a new product, auto-generating an Id
.
Controller Class
The controller is responsible for handling user requests. Here’s the ProductController
to manage displaying and adding products:
// Controllers/ProductController.cs using Microsoft.AspNetCore.Mvc; using ProductApp.Models; namespace ProductApp.Controllers { public class ProductController : Controller { private ProductRepository _repository = new ProductRepository(); public IActionResult Index() { var products = _repository.GetAll(); return View(products); } [HttpPost] public IActionResult Add(string name, decimal price) { if (!string.IsNullOrEmpty(name) && price > 0) { var newProduct = new Product { Name = name, Price = price }; _repository.Add(newProduct); return PartialView("_ProductItem", newProduct); } return BadRequest(); } } }
This controller has two actions: Index()
to display the list of products and Add()
to handle form submissions for adding new products. The Add()
action returns a partial view, which will be used by HTMX to update the page dynamically.
View Class
In the Views/Product/Index.cshtml
, we render the list of products and a form to submit new ones:
// Views/Product/Index.cshtml @model List<ProductApp.Models.Product> <h1>Products</h1> <div> <ul id="productList"> @foreach (var product in Model) { @await Html.PartialAsync("_ProductItem", product) } </ul> </div> <form hx-post="/product/add" hx-target="#productList" hx-swap="beforeend"> <input type="text" name="name" placeholder="Product Name" /> <input type="number" name="price" placeholder="Product Price" step="0.01" /> <button type="submit">Add Product</button> </form> <script src="https://unpkg.com/[email protected]"></script>
This page lists all the products and provides a form to submit new products. The form uses HTMX attributes to submit via Ajax and insert new products without refreshing the page. The response from the Add()
action is inserted directly at the end of the #productList
element.
Partial View for Product Item
The partial view _ProductItem.cshtml
will render each product in a list item:
// Views/Product/_ProductItem.cshtml @model ProductApp.Models.Product <li> @Model.Name - [email protected] </li>
Main Program Configuration
The Program.cs
file sets up the application and configures the routing:
// ProductApp/Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.MapControllerRoute( name: "product", pattern: "product/{action=Index}/{id?}", defaults: new { controller = "Product" }); app.Run();
This file configures the application's HTTP request pipeline and sets up routing for the default and Product
controllers.
This simple application allows users to view a list of products and add new ones dynamically, all without writing any JavaScript. The combination of .NET, C#, and HTMX simplifies building a dynamic, modern web app while adhering to the MVC architecture. Try running the app with $ dotnet run
to see it in action!