Vitraux is a .NET library for manipulating the HTML DOM in WebAssembly applications.
Using a declarative approach, it maps .NET ViewModels directly to standard HTML elements, maintaining a clear separation of concerns between frontend and backend. This makes your code easier to maintain, more readable, and highly scalable.
-
Clean HTML and C#: No need for custom components or mixing .NET logic with HTML. Your frontend remains pure HTML5 and your backend pure .NET.
-
True Decoupling: Keeps business logic (backend) and presentation (frontend) fully separated, making collaboration and maintenance easier.
-
Declarative Mapping: Define the mapping between ViewModels and HTML elements in a simple, expressive way.
-
Easy Integration: Just define your mappings and call an update method. Everything else is handled automatically.
-
Efficient DOM Updates: Works with DOM objects in memory as much as possible, updating the UI only when strictly necessary for optimal performance.
-
Compatible and Scalable: Works with anything from vanilla HTML and Web Components to any frontend tool that outputs HTML.
-
HTML Modularization: Lets you break your HTML into fragments stored in templates or external URIs, which you can design and structure however you like.
-
Change Tracking: Sends and updates only the data that actually changed since the last update, minimizing data transfer and avoiding unnecessary re-renders.
-
Flexible Element Selection Strategies: Multiple ways to select HTML elements, adaptable to any view structure.
-
Custom JavaScript Function Mapping: Call your own JavaScript functions in response to ViewModel changes.
-
Modular Mapping: Split your mapping into sub-mappings for better readability and reuse.
-
Auto-Generated Function Caching: Initialization and update functions generated by Vitraux can be cached for faster load times.
-
Shadow DOM Support: Optionally encapsulate styles and structure using Shadow DOM, depending on project needs.
-
Create a Blazor WebAssembly Standalone Project
Use Visual Studio and disable the “Include sample pages” option, or create it via the .NET CLI:dotnet new blazorwasm --empty -o MyProject
-
Remove all .razor files
Delete all .razor files from your solution so it’s clean and free of Blazor pages or components. -
Remove root component code from Main()
Remove any root component registration and rendering from Main() — Vitraux doesn’t use them. -
Install the Vitraux NuGet package
-
Add the Vitraux JavaScript file
Place vitraux-<version>
-min.js anywhere in your site’s folder structure. There’s no required location
— just ensure your HTML references the correct path. -
Include the JavaScript references in your HTML
Add the vitraux-<version>
-min.js reference in<head>
or before the closing</body>
.
The _framework/blazor.webassembly.js reference must be placed at the end of<body>
:<script src="js/vitraux-<version>-min.js"></script> ... <script src="_framework/blazor.webassembly.js" autostart="false"></script>
-
Add your C# code and a minimal configuration example
// PetOwner.cs public record class PetOwner { public int Id { get; init; } public string Name { get; init; } = string.Empty; public string Address { get; init; } = string.Empty; public string? PhoneNumber { get; init; } public string HtmlComments { get; init; } = string.Empty; public IEnumerable<Pet> Pets { get; init; } = Enumerable.Empty<Pet>(); } // Pet.cs public record class Pet { public string Name { get; init; } = string.Empty; public DateTime DateOfBirth { get; init; } } // PetOwnerConfiguration.cs public class PetOwnerConfiguration : IModelConfiguration<PetOwner> { public ConfigurationBehavior ConfigurationBehavior { get; } = new() { QueryElementStrategy = QueryElementStrategy.OnlyOnceAtStart, TrackChanges = true, VMUpdateFunctionCaching = VMUpdateFunctionCaching.ByVersion("test 1.0") }; public ModelMappingData ConfigureMapping(IModelMapper<PetOwner> modelMapper) => modelMapper .MapValue(po => po.Name).ToElements.ById("petowner-name").ToContent .MapValue(po => po.Address).ToElements.ById("petowner-address").ToContent .MapValue(po => po.PhoneNumber).ToElements.ById("petowner-phone-number").ToContent .MapValue(po => po.HtmlComments).ToElements.ByQuery(".comments").ToHtml .MapCollection(po => po.Pets) .ToTables.ById("petowner-pets") .PopulatingRows.ToTBody(0).FromTemplate("petowner-pet-row") .MapValue(pet => pet.Name).ToElements.ByQuery("[data-id='pet-name']").ToContent .MapValue(pet => pet.DateOfBirth).ToElements.ByQuery("[data-id='pet-date-of-birth']").ToContent .EndCollection .EndCollection .Data; } // Program.cs using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Vitraux; using System.Runtime.InteropServices.JavaScript; public partial class Program { private static IViewUpdater<PetOwner> _petownerViewUpdater = default!; public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); _ = builder.Services .AddVitraux() //add Vitraux .AddConfiguration(() => new VitrauxConfiguration { UseShadowDom = true }) //add a configuration (optional) .AddModelConfiguration<PetOwner, PetOwnerConfiguration>(); //add the model configuration await using var host = builder.Build(); await host.Services.BuildVitraux(); //Build Vitraux _petownerViewUpdater = host.Services.GetRequiredService<IViewUpdater<PetOwner>>(); //Get your view updater await host.RunAsync(); } [JSExport] public static async Task GetPetOwner() { var petOwner = new PetOwner { Id = 1, Name = "Juan Pérez", Address = "Av. Siempre Viva 742", PhoneNumber = "+54 9 11 1234-5678", HtmlComments = "<b>Cliente VIP</b>", Pets = new[] { new Pet { Name = "Boby", DateOfBirth = new DateTime(2021, 5, 1) }, new Pet { Name = "Miau", DateOfBirth = new DateTime(2019, 9, 14) } } }; await _petownerViewUpdater.Update(petOwner); //Update the UI } }
-
Full HTML Example
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Vitraux Example</title> <link rel="stylesheet" href="styles.css" /> <script src="js/vitraux.js"></script> <script> addEventListener("DOMContentLoaded", async () => { await Blazor.start(); const petOwnerWasm = await getPetOwnerWasm(); await petOwnerWasm.GetPetOwner(); async function getPetOwnerWasm() { const { getAssemblyExports } = await globalThis.getDotnetRuntime(0); const exports = await getAssemblyExports("YourProject.dll"); return exports.YourNamespace.Program; } }); </script> </head> <body> <h1>Petowner Example</h1> <div class="info-section"> <div>Owner Information</div> <div id="petowner-name"></div> <div id="petowner-address"></div> <div id="petowner-phone-number"></div> </div> <div class="info-section"> <div>Pets</div> <table id="petowner-pets"> <thead> <tr> <th>Name</th> <th>Date of Birth</th> </tr> </thead> <tbody></tbody> </table> </div> <div class="info-section"> <div>Comments</div> <div class="comments"></div> </div> <template id="petowner-pet-row"> <tr> <td data-id="pet-name"></td> <td data-id="pet-date-of-birth"></td> </tr> </template> <script src="_framework/blazor.webassembly.js" autostart="false"></script> </body> </html>
-
For a complete breakdown of features and technical details, see the Manual de Referencia.
Vitraux uses declarative mapping to connect .NET ViewModels to standard HTML elements in WebAssembly applications. When you need to update the UI, you call an update method in .NET, which sends the data to the browser. Vitraux then runs an automatically generated JavaScript function to update the DOM, keeping your view in sync with your data model.
Install the package from NuGet
Contributions are welcome! If you have ideas, find bugs, or want to collaborate, feel free to open an Issue or Pull Request.
This project is licensed under the MIT License. See the LICENSE file for details.