PropertyComponent
PropertyComponent is the single Blazor component that renders the correct editor or viewer for any entity property. It reads the property's metadata from the server model, resolves the appropriate UI component, and wires up data binding and validation — all from a single Path parameter.
Basic Usage
<PropertyComponent Path="Id" />
<PropertyComponent Path="PrimaryContact.PrimaryEmail" />
<PropertyComponent Path="PrimaryContact.MailingAddress.Country" />
Each PropertyComponent must be inside a DataGraph, which provides the cascading DataGraphContext containing the DataSet, entity metadata, and display mode.
How Resolution Works
When PropertyComponent renders, it follows this resolution process:
- Exact match on
(DataType, DisplayMode)— e.g.,("Email", Edit) - Exact match on
(Type, DisplayMode)— e.g.,("string", Edit) - Fallback without
DataGridflag — e.g.,Editinstead ofDataGrid.Edit - Fallback to base mode —
EditorView
If no component is found, PropertyComponent renders an error indicator.
Built-in Component Mappings
The following types are registered by default when you call AddCoreBlazor():
Scalar Types
| Data Type / CLR Type | Edit Component | View Component |
|---|---|---|
bool |
BooleanPropertyEdit — checkbox |
BooleanPropertyView — read-only checkbox |
string |
StringPropertyEdit — text field |
StringPropertyView — plain text |
int |
NumberPropertyEdit — numeric field |
NumberPropertyView — formatted number |
decimal |
NumberPropertyEdit — numeric field |
NumberPropertyView — formatted number |
double |
NumberPropertyEdit — numeric field |
NumberPropertyView — formatted number |
Date |
DateOnlyPropertyEdit — date picker |
DateOnlyPropertyView — formatted date |
DateTime |
DateTimePropertyEdit — date+time picker |
DateTimePropertyView — formatted date+time |
Specialized String Types
These use StringPropertyEdit in edit mode but have specialized view components:
| Data Type | View Component | Behavior |
|---|---|---|
EmailAddress |
EmailPropertyView |
Renders as clickable mailto link |
PhoneNumber |
PhonePropertyView |
Renders as clickable tel link |
Url |
UrlPropertyView |
Renders as clickable hyperlink |
FullAddress |
FullAddressPropertyView |
Multi-line formatted address |
Enum Types
By default, enum properties use a dropdown. The server synthesizes DataType = "Enum" for enum
properties and ships the member names in EnumValues (the dropdown's options) together with the
corresponding numeric values in EnumUnderlyingValues. Values round-trip as the enum member name
(a string).
| Data Type | Edit Component | View Component |
|---|---|---|
Enum |
EnumPropertyEdit — dropdown of enum members |
EnumPropertyView — selected member name |
EnumRadio |
EnumRadioPropertyEdit — radio buttons for enum members |
EnumPropertyView — selected member name |
Use [DataType("EnumRadio")] to show radio buttons for an enum property:
using System.ComponentModel.DataAnnotations;
[DataType("EnumRadio")]
public OrderStatus Status { get; set; }
<PropertyComponent Path="Status" />
The editor uses the enum metadata for its choices. It humanizes labels and saves the original member name.
Nullable properties include a Clear button.
Property labels, validation, and read-only behavior follow the standard property context.
Use EnumRadio only on enum properties. Enums without this annotation keep their dropdown editor.
Radio options use horizontal layout by default. Override the editor to stack them vertically:
@using Benevia.Core.Blazor.DynamicComponents.Simple
<PropertyComponent Path="Status">
<EnumRadioPropertyEdit RadioVertical="true" />
</PropertyComponent>
Set RadioVertical="false" for horizontal layout. Both layouts keep the standard property context and binding.
Configure the shared appearance on the editor. Pages do not need CSS for radio cards:
<PropertyComponent Path="Status">
<EnumRadioPropertyEdit RadioVertical="true" Variant="Variant.Outlined"
Size="Size.Small" Color="Color.Primary" Dense="true" />
</PropertyComponent>
| Parameter | Default | Behavior |
|---|---|---|
RadioVertical |
false |
Vertical options when true; horizontal options wrap when space is limited. |
Variant |
Variant.Text |
Plain radios, outlined cards, or filled cards. |
Size |
Size.Medium |
Radio icon size and minimum card height. |
Color |
Color.Primary |
Theme color for selected radios, card borders, hover, and keyboard focus. |
Dense |
true |
Compact spacing; false increases padding and field margin. |
Cards use theme colors and support disabled state, keyboard focus, and wrapping labels.
Navigation Types
| Data Type | Edit Component | View Component |
|---|---|---|
Reference |
ReferencePropertyEdit — entity selector/autocomplete |
ReferencePropertyView — display text with optional link |
Picker |
ReferencePropertyEditPicker — dropdown selector |
ReferencePropertyView |
Collection |
CollectionPropertyEdit — editable data grid |
CollectionPropertyView — read-only table |
PropertyComponent Parameters
| Parameter | Type | Description |
|---|---|---|
Path |
string (required) |
Property path in the DataSet (e.g., "PrimaryContact.PrimaryEmail") |
ShowLabel |
bool? |
Override label visibility (null = auto based on display mode) |
ShowColon |
bool? |
Show the label with a trailing colon ("Label:"); implies the label is shown. Default false. |
DisplayMode |
DisplayMode? |
Override the display mode from the parent DataGraph |
ChildContent |
RenderFragment<PropertyComponentContext> |
Custom component override |
PropertyComponentContext
Every property component (built-in or custom) receives a PropertyComponentContext via cascading parameter. This context bundles the DataSet and the property's metadata:
| Member | Description |
|---|---|
Get<T>() |
Read the current value from the DataSet |
Set(value) |
Write a value (triggers DataSet.SetAsync asynchronously) |
IsReadOnly() |
Whether the property is currently read-only (from server's RecordReadOnly) |
GetShortMessage() |
First validation error/prompt for this property |
GetLabel() |
Formatted label from metadata (e.g., "Primary Email") |
IsDataGridDisplayMode() |
Whether we're rendering inside a collection grid |
Custom Rendering with ChildContent
Override the default component for a specific property by providing ChildContent:
<PropertyComponent Path="PrimaryContact.Note">
<MudTextField Value="@context.Get<string>()"
ValueChanged="@(value => context.Set(value))"
Disabled="@context.IsReadOnly()"
Label="@context.GetLabel()"
Lines="5"
Variant="Variant.Outlined" />
</PropertyComponent>
The context variable is a PropertyComponentContext — you can read/write values and access metadata through it. Use Value with ValueChanged to enable both read and write binding.
Access checks
Custom editors must bind Disabled to context.IsReadOnly().
Blocked writes show a warning and restore the field's value.
For client code outside a form, use the shared helpers:
var canSelect = ClientCapabilities.IsQueryPathReadable(entity, "Amount", access);
var canEdit = ClientCapabilities.CanWrite(entity, "Amount", access, isNew: false);
Wrap commands that create another entity, such as a New order link, in CreateAccessView. It shows its
content only when the entity's feature is enabled and the user may create it:
<CreateAccessView Entity="SalesOrder">
<MudButton Href="/sales-orders/new">New order</MudButton>
</CreateAccessView>
Code can ask the same question through the scoped EntityCreateAccess.CanCreateAsync(entityName).
Query restrictions do not change write capabilities. Form controls also check record business rules.
Blank-record editors require Read, Create, Update, and a readable GUID.
See API access.
Reference Property Components
Reference properties (foreign keys to other entities) render as entity selectors:
@* Default: autocomplete search with server-side OData $search *@
<PropertyComponent Path="BillingCustomer" />
The ReferencePropertyEdit component:
- Displays the referenced entity's title (or configured display property)
- Provides autocomplete search against the server
- Writes the selected entity's GUID back to the DataSet (
BillingCustomerGuid) - Shows a lookup adornment to navigate to the referenced entity
Lookup Routes
The lookup adornment needs no LookUpUrl. ReferencePropertyEdit, ReferencePropertyEditPicker and
ReferencePropertyView ask EntityPageRoutes for the page that opens one record of the referenced
entity, which is the page carrying the route /{EntityName}/{key:guid}:
@page "/Customer/{CustomerGuid:guid}"
Give every entity page that route and every reference to it gets a lookup. An entity without one gets
no lookup, so the adornment never points at a page that does not exist. Pass LookUpUrl only to send
the lookup somewhere other than that page.
Advanced Search
References built on CoreEntitySelector can open the referenced entity's list in a popover, from
the search button in the field or with F4. The user can search, filter and use their saved views
there, and picking a row sets the reference. The X in the popover's top-right corner, Esc, or
clicking outside closes it without changing the value. Like the type-ahead, the list only offers
the rows the reference accepts. The list opens under the field, lined up with its left edge, or with
its right edge when the field is too close to the right side of the window. The user can drag its
bottom-right corner to resize it; it opens at its usual size again next time.
The list's search box takes the keyboard when it opens, holding what was typed in the field. ↓
there moves on to the rows, where ↑/↓ move and Enter picks the highlighted row. A grouped list
is browsed with the mouse. A line under the list shows these keys.
An entity registered with AddEntityList opens with its list page's columns and saved views (see
ListView). Any other entity opens a list of the fields the
selector shows. A selector given a fixed Items list has no advanced search. With a SearchFunc,
set DisplayPath, HelperTextPath and AdditionalSelectPaths so a picked row matches its results.
The list also loads those fields, hidden, so a picked row is used as it is, without loading it again. The popover closes as soon as a row is picked, while the page saves the new value.
Creating a Missing Reference
AddCoreBlazor() enables Create after a successful empty reference search with model, feature, and Create access.
Lookup discovery requires an entity record alias, one literal /new route, and a writable nullable Guid parameter.
Query-bound properties must also be public writable [Parameter] properties, including inherited declarations.
Read-only references, local items, and custom searches skip Create; browsing selectors can set AllowAutomaticCreation="false".
The existing .Reference("Product", "Sku") display field or selector DisplayPath seeds an empty, persisted,
writable string included in the creation graph after initialization; other fields are skipped without a fallback.
The drawer isolates query defaults and workflow, keeps validation failures open, and returns committed keys without OnSaved.
Child tabs, cancellation, and navigation preserve the parent draft and URL. Saved children persist independently.
The selector reloads through current reference filters with its normal display/helper fields and rejects stale results.
Customizing Reference Display
By default, references show the Title property. To customize, use ChildContent:
<PropertyComponent Path="BillingCustomer">
<ReferencePropertyEdit DisplayProperty="Id" Clearable="true"
LookUpUrl="/customers" />
</PropertyComponent>
Collection Property Components
Collection properties (one-to-many navigations) render as data grids:
<PropertyComponent Path="AdditionalContacts" />
CollectionPropertyEdit renders a table with:
- One column per property in the navigation's graph definition
- Inline editing via
PropertyComponentfor each cell - Add Row and Delete Row actions
- Each row backed by a child
IDataSetfromDataSetCollection
Column Templates
Customize individual columns using ColumnTemplate:
<PropertyComponent Path="AdditionalContacts">
<CollectionPropertyEdit>
<ColumnTemplate Property="Role" Label="Contact Role">
<ReferencePropertyEditPicker DisplayProperty="Name" />
</ColumnTemplate>
</CollectionPropertyEdit>
</PropertyComponent>
Row Actions
Add custom actions to each row:
<PropertyComponent Path="AdditionalContacts">
<CollectionPropertyEdit>
<CollectionRowAction Label="View Contact"
Icon="@Lucide.ExternalLink.Markup"
OnClick="@(item => NavigateToContact(item))" />
</CollectionPropertyEdit>
</PropertyComponent>
Display Modes
PropertyComponent supports three display modes:
| Mode | Behavior |
|---|---|
DisplayMode.Edit |
Renders interactive editors (text fields, pickers, checkboxes) |
DisplayMode.View |
Renders read-only displays (text, links, formatted values) |
DisplayMode.DataGrid |
Compact editors optimized for collection grid cells (no labels, smaller variant) |
The display mode is set on DataGraph and cascaded to all children. Individual PropertyComponent instances can override it:
<DataGraph Builder="@BuildGraph" DataSet="@_dataSet" EntityGuid="@guid" DisplayMode="DisplayMode.Edit">
<PropertyComponent Path="Id" /> @* Edit mode *@
<PropertyComponent Path="FullName" DisplayMode="DisplayMode.View" /> @* Forced View mode *@
</DataGraph>
Labels and the Colon
Two flags control the label on a View-mode label-before-value component (StringPropertyView, EmailPropertyView, ReferencePropertyView, …):
ShowLabel— shows the label as a plain word:Emailalice@example.com.ShowColon— shows the label with a trailing colon:Email:alice@example.com. SettingShowColonimplies the label is shown, so you don't also needShowLabel. Default isfalse.
<PropertyComponent Path="Email" /> @* value only (View mode hides labels by default) *@
<PropertyComponent Path="Email" ShowLabel="true" /> @* Email alice@example.com *@
<PropertyComponent Path="Email" ShowColon="true" /> @* Email: alice@example.com *@
The colon is presentation added by the component, never part of the model label, and it applies only to label-before-value View components — edit-mode floating field labels, data-grid headers, and BooleanPropertyView stay colon-free regardless of ShowColon.
Labels themselves come from the model: the property's Label, falling back to the humanized property name (AccountNumber → Account Number). If the displayed wording is wrong, change the model label rather than overriding it on the client. For a full read-only "Label: value" list, use GroupLayout.Detail.
Change Handling
PropertyComponent subscribes to DataSet.Changed events internally. When a change affects its path, it re-renders automatically. You don't need to wire up change handling for individual properties — it happens behind the scenes.
The re-render is targeted: only properties affected by the change update. This is efficient even on pages with many properties.
MethodButton
MethodButton runs a method with no inputs on the graph's current record.
Use Label to set the button text. Otherwise, it uses the server label, then the method name.
The button is hidden without permission. When the server disables it, it stays visible but greyed out.
<MethodButton Method="ApplySurcharge" />
Read methods use GET. Modify methods use POST.
Set DataSet to use another record of the same entity type, or OnClick to handle the click yourself.
See Method Buttons for examples.
See Property Groups for group layout, nested groups, and inline property or group overrides.
Shared Editor Behaviors
These behaviors ship with the built-in editors, so pages get them without extra wiring:
- Clear buttons.
StringPropertyEditandNumberPropertyEditrender a clear button outside grid cells. Clearing a number writesnull; clearing a string writes""(OData rejectsnulloutright for a required string).CoreEntitySelector's clear button is hidden until the field is hovered or focused. - Format override.
<NumberPropertyEdit Format="N3" Step="0.01m" />overrides the metadata format when the precision is only known at runtime. - Helper text.
<NumberPropertyEdit HelperText="@context.PropertyMetaData.Description" />shows a sentence under the field outside grid cells. A validation error takes its place while the value is rejected. - Enum labels. Display text is humanized ("PartiallyFulfilled" → "Partially Fulfilled") via
LabelFormatter.FormatLabel; the bound value stays the raw member name.LabelFormatter.FormatPropertyLabel(PropertyMetaData)returns the declared label, else the humanized property name. - Row actions menu.
CoreActionsMenuis the shared three-dot menu: one icon and one neutral color everywhere. Screens style the popover content, never the trigger. - Ctrl+Enter adds a row. Collection grids register with
KeyboardEventService(RegisterAddRow/SyncAddRow); the focused grid wins, otherwise the most recently registered visible grid. Hidden tab panels are excluded. The shortcut is ignored while typing in an editor outside a grid.