Disambiguation
What it is
By default, the IObjectModelValidator installed by
ConfigureMrxModelBindingValidation
re-keys every validation error as a JSON path relative to the model it was raised on — for example
id for a property named id, regardless of whether that property came from the route, the query
string, or the request body. This is enough as long as no two binding sources on the same endpoint
expose a field with the same name.
When they do — say, an endpoint that binds an id route parameter and accepts a body with its
own id property — both validation errors would end up under the same key, id, with no way to
tell which source they came from.
Disambiguation solves this by prefixing every error key with the display name of the binding
source it came from (Route.id, Body.id, Query.id, …), making colliding keys distinguishable.
How to enable it
Disambiguation is opt-in, per controller or per action, via
DisambiguateAttribute:
[Disambiguate]
[HttpPut("{id}")]
public IActionResult UpdateOrder(int id, [FromBody] UpdateOrderRequest body) { ... }
Applying the attribute to a controller enables disambiguation for all of its actions; applying it to an action enables it just for that action.
Whether disambiguation is active for a given request is also reported back to the client via the
X-Disambiguated response header ("true" or "false"), so that clients don't need to hardcode
per-endpoint assumptions — see Feature Discovery for how this can be
discovered ahead of time as well.
No Razor markup changes required
Disambiguation is entirely a server-side key-formatting concern. On the client,
MRX.UI.ErrorsAutoWiring reads
the X-Disambiguated header itself: when it's true, it splits each error key on its first . to
recover the binding source and the remaining path, resolves that path against the matching
DataBindingSources member (Body or
Query), and populates the EditContext's validation messages exactly as it would without
disambiguation. Your components, bindings, and <ValidationMessage For="..."/> elements don't need
to know or care whether disambiguation is on for a given endpoint.
The risk of leaving it disabled with colliding names
When disambiguation is not enabled, the client has no information telling it which binding
source an error key belongs to, so
DefaultErrorMappingFactory
resolves every error key against every binding source model in turn (Body, then Query). If a
field name collides across sources — e.g. id exists on both the route-bound parameter and the
body model — the same error entry gets attached to both fields' FieldIdentifier, because the
client has no way to tell they aren't the same field.
In practice, this means: if colliding field names exist across binding sources on an endpoint
that does not have [Disambiguate] applied, all merged errors for that key will be displayed next
to every field sharing that name, not just the one the error actually applies to. This can
surface confusing or incorrect validation messages to the user.
To avoid this, apply [Disambiguate] to any controller or action where binding sources can
plausibly share field names. If you're unsure whether an endpoint's binding sources can collide, it
is always safe to enable disambiguation — it has no effect on the client-side developer experience
beyond correctly separating otherwise-ambiguous keys.