Generated forms

edit

GenericForm<TModel> generates a label, an editor and a validation message for each property of a model. Put it in an EditForm:

@using System.ComponentModel
@using System.ComponentModel.DataAnnotations

<EditForm Model="contact" OnValidSubmit="Save">
    <DataAnnotationsValidator />
    <GenericForm @bind-Model="contact" EditorClass="form-control" />
    <button type="submit">Save</button>
</EditForm>

@code {
    private Contact contact = new();

    private void Save()
    {
        // contact contains the values the user entered
    }

    public sealed class Contact
    {
        [Required]
        [Display(Name = "Full name", Order = 1, Prompt = "Jane Doe")]
        public string? Name { get; set; }

        [DataType(DataType.EmailAddress)]
        [Display(Order = 2)]
        public string? Email { get; set; }

        [DataType(DataType.Date)]
        public DateTime? BirthDate { get; set; }

        public ContactKind Kind { get; set; }

        public bool Subscribed { get; set; }

        [Editor(typeof(InputTextArea), typeof(InputBase<>))]
        public string? Notes { get; set; }

        [Editable(false)]
        public Guid Id { get; set; } = Guid.NewGuid(); // Not shown in the form

        public DateTimeOffset CreatedAt { get; } = DateTimeOffset.UtcNow; // Read-only: not shown either
    }

    public enum ContactKind
    {
        Personal,
        [Display(Name = "Work contact")]
        Work,
    }
}
  • @bind-Model (or Model and ModelChanged) gives the instance to edit. ModelChanged is raised each time a field changes.
  • EditorClass is the CSS class of every editor, such as form-control.
  • EnableFieldValidation (true by default) renders a ValidationMessage after each editor. The validation messages need the EditContext of an EditForm.

The form edits the instance in place: a property of contact is updated as soon as the user changes its editor.

Which properties become fields

Every public instance property with a setter becomes a field, including inherited ones. A property is left out when it has no setter or has [Editable(false)].

AttributeEffect
[Display(Name = "...")] or [DisplayName("...")]The label. The property name is used otherwise
[Display(Order = n)]The position of the field. Fields without an order come after the ordered ones whose order is below 10000
[Display(Prompt = "...")]The placeholder of the editor
[Display(Description = "...")] or [Description("...")]Available to a custom template as field.Description
[Editable(false)]Removes the field
[Editor(typeof(MyEditor), typeof(InputBase<>))]Uses MyEditor as the editor

Which editor is used

The editor comes from an [Editor] attribute on the property, then from an [Editor] attribute on the type of the property, then from the property type:

Property typeEditor
boolInputCheckbox
stringInputText. With [DataType]: type="date", "datetime-local", "email", "password", "tel", "time" or "url", and InputTextArea for DataType.MultilineText
short, int, long, float, double, decimalInputNumber
DateTime, DateTimeOffsetInputDateTime, or InputDate with [DataType(DataType.Date)]
GuidInputGuid
UriInputUrl
An enum without [Flags]InputEnumSelect
Anything else, including [Flags] enumsA text input

Nullable value types (int?, DateTime?, Guid?, enums) get the same editor as the type they wrap.

The editor of an [Editor] attribute is any component that derives from InputBase<T>, for the type of the property. It receives Value, ValueChanged, ValueExpression, id, class and placeholder.

Customize the layout

By default, each field renders a <div> with the <label>, the editor and the validation message. FieldTemplate renders each field yourself; its context is a GenericFormField<TModel>:

@using System.ComponentModel.DataAnnotations

<EditForm Model="settings">
    <DataAnnotationsValidator />
    <GenericForm Model="settings" EditorClass="form-control">
        <FieldTemplate Context="field">
            <div class="mb-3">
                <label class="form-label" for="@field.EditorId">@field.DisplayName</label>
                @field.EditorTemplate
                @if (field.Description is not null)
                {
                    <div class="form-text">@field.Description</div>
                }
                @field.FieldValidationTemplate
            </div>
        </FieldTemplate>
    </GenericForm>
</EditForm>

@code {
    private readonly Settings settings = new();

    public sealed class Settings
    {
        [Range(1, 100)]
        [Display(Name = "Page size", Description = "Number of items shown on each page")]
        public int PageSize { get; set; } = 20;

        [Display(Description = "Address of the API, for instance https://api.example.com")]
        public Uri? ApiUrl { get; set; }
    }
}
MemberContent
DisplayName, Description, Prompt, DisplayOrderThe metadata of the attributes above
EditorIdThe id of the editor, for the for attribute of a label
EditorTemplateThe editor
FieldValidationTemplateThe validation message, or null when EnableFieldValidation is false
Property, PropertyType, Value, OwnerThe property, its type, its current value and the model

The fields are discovered with reflection over TModel, and they are rebuilt only when another model instance is assigned.