ReadOnly Event
Summary
The ReadOnly event controls whether a property can be written to. When a read-only condition evaluates to true, the write is refused: an error prompt is recorded against the property and a PropertyWriteRejectedException interrupts the set. Multiple read-only subscribers can exist on the same property — if any condition is true, the property is read-only.
Hidden properties are implicitly read-only: a property whose Hidden condition is currently true reports IsReadOnly == true and is unioned into the RecordReadOnly CSV, without needing a separate ReadOnly rule. A write to it is rejected with the more specific Hidden reason.
Over the API this surfaces as an ordinary business rejection — HTTP 400 carrying the prompt, the same shape a validation failure returns — not a server error. The Error prompt also blocks the save, so nothing in the request commits.
When does it fire?
ApiAccess blocks client writes with HTTP 403 and leaves server setters available.
The ReadOnly event also checks server writes.
Read-only checks are evaluated when a property setter is called, before any other processing (AutoCorrect, Validate, etc.):
Set property → ReadOnly? → Yes → Record error prompt + reject the write (API: 400)
→ No → Continue (AutoCorrect → Validate → ...)
Read-only state is also evaluated when the API retrieves read-only metadata for the UI. This means read-only conditions are called frequently and must be extremely fast.
Syntax
salesOrderDetail.ReadOnly(d => d.Quantity)
.If(d => d.SalesOrder != null && d.SalesOrder.Status == SalesOrderStatus.Closed);
Fluent API
| Step | Method | Description |
|---|---|---|
| Required | .If(condition) |
Condition that, when true, makes the property read-only |
Property groups
Apply one condition to explicitly selected group members:
financialDoc.PropertyGroup<PostedLock>().ReadOnly().If(doc => doc.IsPosted);
The group also accepts args => args.Entity.IsPosted and covers members added later during startup.
It skips setterless members. Group and individual property rules combine with OR.
Keep IsPosted outside the lock group when users must unpost.
Scenarios
1. Locking the properties on a Sales Order when the order is finalized
Properties on the sales order are also locked once the order is finalized.
[Logic]
public class SalesOrderFinalizeBL(SalesOrder.Logic salesOrder, SalesOrderDetail.Logic salesOrderDetail)
{
[RegisterLogic]
public void ReadOnlyWhenFinalized()
{
salesOrder.ReadOnly(o => o.SellToCustomer).If(IsOrderFinalized);
salesOrder.ReadOnly(o => o.OrderDate).If(IsOrderFinalized);
salesOrder.ReadOnly(o => o.ShippingAmount).If(IsOrderFinalized);
salesOrderDetail.ReadOnly(d => d.Product).If(IsOrderFinalized);
salesOrderDetail.ReadOnly(d => d.Description).If(IsOrderFinalized);
salesOrderDetail.ReadOnly(d => d.Quantity).If(IsOrderFinalized);
salesOrderDetail.ReadOnly(d => d.UnitPrice).If(IsOrderFinalized);
}
static bool IsOrderFinalized(SalesOrder o) =>
o.Status == SalesOrderStatus.Shipped || o.Status == SalesOrderStatus.Closed;
static bool IsOrderFinalized(SalesOrderDetail d) =>
d.SalesOrder != null
&& (d.SalesOrder.Status == SalesOrderStatus.Shipped
|| d.SalesOrder.Status == SalesOrderStatus.Closed);
}
2. Multiple read-only conditions on the same property
A property can have multiple read-only rules. If any condition is true, the property is read-only.
[Logic]
public class SalesOrderDetailBL(SalesOrderDetail.Logic salesOrderDetail)
{
[RegisterLogic]
public void ReadOnlyOnOrderStatus()
{
salesOrderDetail.Quantity.ReadOnly()
.If(d => d.SalesOrder != null && d.SalesOrder.Status == SalesOrderStatus.Closed);
}
[RegisterLogic]
public void ReadOnlyOnDetailShipped()
{
// Also read-only when the detail line has been shipped
salesOrderDetail.Quantity.ReadOnly()
.If(d => d.IsShipped);
}
}
3. Checking read-only state before setting a property
When business logic needs to set a property that might be read-only, check the state first.
var isReadOnly = orderDetail._State.Quantity.IsReadOnly;
if (!isReadOnly)
orderDetail.Quantity = newQuantity;
Performance
Read-only conditions are evaluated on every property assignment and when the API retrieves property metadata. Your condition must be extremely fast:
- Do: Reference in-memory properties on the entity or its parent.
- Do not: Make database calls, LINQ queries, or expensive service calls. If you need data from the database or a service, compute it once into a property and reference that property in the read-only condition. This will only compute when necessary.
Notes
- Attempting to set a read-only property records an Error prompt for that property and throws
PropertyWriteRejectedException(a subtype ofInvalidOperationException). The API reports it as a 400 with the prompt; the property keeps its previous value. - The read-only state is tracked on
_State.<Property>.IsReadOnlyand is available to the API so the UI can disable fields. - Read-only conditions are cumulative — multiple subscribers combine with OR logic.
RecordReadOnlymerges three sources: accessed read-only properties, currently hidden properties (hidden implies read-only, included regardless of$select), and disabled method names.