Lumeo

[LumeoForm] Source Generator

Hand-writing a Blazor form for every POCO — picking the right input per property, wiring @bind-Value, repeating <FormField Label="…" Required> — is pure boilerplate. The [LumeoForm] Roslyn source generator does it for you at compile time.

Annotate any class with [LumeoForm] and make it partial. The generator emits a static RenderForm(model, onValidSubmit) method that returns a ready-to-use RenderFragment.

1. Annotate a POCO

using System.ComponentModel.DataAnnotations;
using Lumeo;

[LumeoForm(Title = "Contact us", SubmitLabel = "Send message")]
public partial class ContactFormModel
{
    [Required, StringLength(80, MinimumLength = 2)]
    [Display(Name = "Full name", Description = "As it should appear on the invoice.")]
    public string Name { get; set; } = "";

    [Required, DataType(DataType.EmailAddress)]
    public string Email { get; set; } = "";

    [DataType(DataType.Password), StringLength(64, MinimumLength = 8)]
    public string Password { get; set; } = "";

    [Range(1, 120)]
    public int Age { get; set; } = 25;

    public bool Subscribe { get; set; }
    public ContactPreference Preference { get; set; }
}

public enum ContactPreference { Email, Phone, SmsMessage }

2. Render it

Use the generated RenderForm method as a RenderFragment — either assign it to a field and invoke it as @_formFragment or render it directly.

@page "/contact"
@using MyApp.Models

<h1>Contact</h1>

@ContactFormModel.RenderForm(_model, EventCallback.Factory.Create<ContactFormModel>(this, HandleSubmit))

@code {
    private readonly ContactFormModel _model = new();

    private Task HandleSubmit(ContactFormModel m)
    {
        // _model is validated — persist it.
        return Task.CompletedTask;
    }
}

3. Live demo

The form below was generated from ContactFormModel at build time — no hand-written Razor.

Contact us

As it should appear on the invoice.

Supported property types

Property type Generated component Notes
string <Input /> Also honours [DataType(DataType.EmailAddress)]type="email".
string + [DataType(Password)] <PasswordInput /> Masked entry, strength meter.
int, long, double, decimal, … <NumberInput /> Coerces to/from double? internally.
bool <Checkbox /> Bound via Checked / CheckedChanged.
DateTime, DateTimeOffset <DatePicker /> Uses the picker's DateTimeValue binding.
DateOnly <DatePicker /> Default DateOnly? binding.
enum <Select /> + one <SelectItem /> per member Labels PascalCase-split to "Sms Message".

Validation attributes

The generator wires the built-in DataAnnotationsFormValidator into every generated form, so any standard System.ComponentModel.DataAnnotations attribute runs at submit time without extra code:

  • [Required] — also marks the field visually via FormField.Required.
  • [StringLength(max, MinimumLength = n)]
  • [Range(min, max)]
  • [DataType(DataType.EmailAddress)] / [DataType(DataType.Password)] — influences input choice.
  • [Display(Name = "…", Description = "…")]Name → field label, Description → help text.

Attribute options

Property Type Default Description
Title string? null Optional heading rendered above the form.
IncludeSubmitButton bool true Whether to append a submit button at the end.
SubmitLabel string "Submit" Label on the generated submit button.

Known limitations

  • Target classes must be marked partial.
  • Only public read/write properties with a public setter are rendered.
  • Collections, nested records, and custom value types are skipped — use a hand-written form for those.
  • Flag enums render as a single Select; use a hand-written ToggleGroup if you need multi-select.
  • Horizontal layout, field ordering, and custom input components require a manual form (the generator emits a vertical stack).