Skip to content

Getting started

One package, one service registration and one stylesheet - then an annotated model becomes a working form: labelled, validated, accessible, and handed back to you populated once it is valid.

Install

Bit.Borm is the whole engine - the schema, the builders, JSON Schema import and export, the state, the rules, the components and the stylesheet - with no dependency beyond Blazor itself. It targets .NET 8, 9 and 10. FluentValidation support is a separate, optional package.

dotnet add package Bit.Borm
# only if your models already have FluentValidation validators
dotnet add package Bit.Borm.FluentValidation

Every public type lives in one namespace, prefixed Borm, so one line in _Imports.razor brings them all into scope:

@using Bit.Borm

Register the services

AddBorm() registers the field renderer registry - the map from a field type to the component that draws it. Register it in every container that renders a form: in a Blazor Web App that is both the server and the client project, because every page is prerendered on the server first.

// Program.cs - in every project that renders a form
builder.Services.AddBorm();

Link the stylesheet

It is not injected for you. Without it a form still works, as plain HTML; the stylesheet adds the theme, the grid and the error colours. There is no script to add - Borm has no JavaScript at all.

<link rel="stylesheet" href="_content/Bit.Borm/bit-borm.css" />

A first form

Annotate a plain model and let the schema generator do the work. BormView builds a BormState for the definition and the model, renders every field, and calls OnValidSubmit once the submit passes validation.

public class Contact
{
    [Required, MaxLength(80)]
    public string Name { get; set; } = "";

    [Required, EmailAddress]
    public string Email { get; set; } = "";

    [Range(18, 120)]
    public int Age { get; set; }
}
<BormView Definition="_definition"
          Model="_model"
          OnValidSubmit="HandleValidSubmit" />

@code {
    private readonly BormDefinition _definition = BormSchemaGenerator.Generate<Contact>();
    private readonly Contact _model = new();

    private Task HandleValidSubmit(BormState state)
    {
        // _model is populated and valid here
        return Task.CompletedTask;
    }
}
Live

Supply a State instead of a Definition when the page wants to hold the state itself - to wire up a validator, read its dirty fields or drive it from code. Every DOM id the form renders starts with borm_ and is derived from the field's path, so two forms on one page that share field names each need their own IdPrefix.

Hosting models

Borm has no JavaScript and no browser-only API, so it runs the same way under every Blazor hosting model. A form prerenders as plain HTML and becomes interactive once the page does - give the page an interactive render mode, since a form in static server rendering cannot respond to input.

  • Blazor WebAssembly - AddBorm() in Program.cs, the stylesheet in index.html.
  • Blazor Server - the same, in the server's Program.cs and App.razor. A field that updates on input sends every keystroke over the circuit, so prefer the default update-on-change, or a debounce, on a busy form.
  • Blazor Web App - AddBorm() in both the server and the client project, so both containers resolve the renderer registry.
  • Blazor Hybrid - AddBorm() in MauiProgram.cs, the stylesheet in wwwroot/index.html.

Where to go next

  • From a C# type - what the generator reads from a model and its attributes.
  • Fluent builder + conditions - typed, refactor-safe field configuration, and fields that show and hide.
  • JSON Schema - a form from a document the server owns, and back out again.
  • Validation modes - when fields revalidate, the error summary, and async rules.
  • MCP server - point an AI agent at this site and it reads the library instead of guessing.
An unhandled error has occurred. Reload 🗙