Client Concepts

Metadata-driven UI

Every [ApiEntity] in Benevia exposes structure through /api/entitymetadata.

This metadata includes:

  • Labels from [Property<T>("...")]
  • Data types such as Email, Phone, Date, and ProperNoun
  • Validation rules such as Required, MaxLength, and MinLength
  • Navigation relationships (references and collections)
  • Read-only rules

Benevia.Core.Client loads and caches metadata. Benevia.Core.Blazor uses it to resolve controls, labels, and validation automatically.

Data graph

A data graph is the declared shape of data for a specific screen or workflow.

It is not the full entity model. It is the subset you ask for: root entity, scalar properties, references, and nested navigations.

You build it with GraphBuilder, then use it with DataSet and DataGraph.

var customerGraph = new GraphBuilder("Customer")
	.Property("Id")
	.Property("FullName")
	.Reference("BillingCustomer")
	.Navigation("PrimaryContact", pc => pc
		.Property("PrimaryEmail")
		.Property("PrimaryPhone"));

The data graph drives three important behaviors:

  • Query shape: controls the OData $select and $expand sent by load/create operations.
  • UI context: defines which property paths are available to components such as PropertyComponent.
  • Update boundaries: keeps page-level state focused so mutation, refresh, and prompt handling stay scoped to what the page owns.

Think of a data graph as a contract between page and server: the page declares what it needs, and the client stack loads, tracks, and updates exactly that shape.

For detailed graph authoring and runtime behavior, see Graph Definitions, DataSets, and DataGraph.

Easy and consistent property components

Traditional UI must repeatedly choose controls and duplicate labels/validation per page. Metadata-driven UI replaces that with stable, declarative property paths. To display a property, you simply give the path to the data graph.

<PropertyComponent Path="PrimaryContact.PrimaryEmail" />
<PropertyComponent Path="PrimaryContact.PrimaryPhone" />
<PropertyComponent Path="BirthDate" />

Permissions aware

  • Read-only: If a property made read-only or a method is disabled, the UI reflects this. (See server Read-only event and Method event)
  • User permissions: If a user's role does not allow permissions, the property or method will not show in the UI. (See Role based access control)

Why this matters

Benefit How
No label duplication Labels are declared once in the server model
Correct control selection Data type mappings choose email, date, checkbox, and other editors automatically
Server-driven validation Validation and read-only state flow from metadata and server responses
Consistent UX The same property renders consistently across pages
Shared view/edit model PropertyComponent switches by display mode rather than custom page logic
Permissions UI components comply with server permissions

Where metadata comes from

Entity model

[ApiEntity]
[NaturalKey(nameof(Id))]
public partial class Customer : IContactAccount
{
	[Required]
	[Property<DataTypes.IdText>("Id")]
	public partial string Id { get; set; }

	[Property<DataTypes.ProperNoun>("Full name")]
	public partial string FullName { get; }

	[ReferenceProperty("Billing customer", DeleteAction.SetNull)]
	public virtual partial Customer? BillingCustomer { get; set; }

	[Method("Get destination contacts", MethodType.Read)]
	public partial IQueryable<Contact> GetDestinationContacts();
}

Event-driven logic

[Logic]
public class CustomerBL(Customer.Logic customer)
{
	[RegisterLogic]
	public void ComputeFullName()
	{
		customer.Compute(c => c.FullName)
			.From(c => c.PrimaryContact != null
				? $"{c.PrimaryContact.FirstName} {c.PrimaryContact.LastName}".Trim()
				: string.Empty)
			.DirtyWithRelation(c => c.PrimaryContact)
			.DirtyBy(c => new { c.FirstName, c.LastName });
	}

	[RegisterLogic]
	public void ValidateId()
	{
		customer.Validate(c => c.Id)
			.RejectIf(id => string.IsNullOrWhiteSpace(id))
			.WithMessage("Customer ID is required.");
	}
}

When SetAsync updates a value, server events compute derived values and run validation. The response returns updated values and prompts, and the dataset updates the UI state.

Package model

Package Purpose
Benevia.Core.Client UI-agnostic foundation: OData client, graph definitions, datasets, model metadata
Benevia.Core.Blazor Blazor UI layer: DataGraph, PropertyComponent, reference pickers, collection grids

Benevia.Core.Client can be used without Blazor. Benevia.Core.Blazor builds on it for dynamic component rendering.