Table of Contents

Class InputOriginAttribute

Namespace
Trellis.Asp
Assembly
Trellis.Asp.dll

Overrides where validation failures that reach this endpoint without a location of their own are said to have come from.

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InputOriginAttribute : Attribute
Inheritance
InputOriginAttribute
Inherited Members
Extension Methods

Examples

// Only needed because this endpoint's residual is not what its binding map implies.
[HttpPost("{id:AccountId}/reconcile")]
[InputOrigin(InputLocation.Unspecified)]
public Task<ActionResult<AccountResponse>> Reconcile(AccountId id) => ...

Remarks

Most endpoints do not need this. A domain producer names the field that failed but cannot know where the value came from — the same aggregate method is reachable from a worker, a message handler, or a test — so it emits Unspecified rather than asserting a checkable claim that may be false. The response boundary resolves those from the endpoint's own binding map, which the framework already builds: a violation naming one of the endpoint's route parameters projects as path, one naming a query parameter it binds projects as query, and anything left over projects as body when the endpoint binds a body at all. That is evidence rather than inference, and it is read identically for a controller action and a minimal API endpoint, so the two hosts cannot drift apart on the wire.

This attribute exists for the cases derivation cannot reach:

  • an application that never registered ApiExplorer, where nothing can be derived and Body or Query supplies the residual;
  • an endpoint whose derived residual is wrong — it binds a body, but its unlocated violations describe something else;
  • Unspecified, to opt out and keep projecting unknown.

It only ever supplies the residual — the names the URL does not account for. The URL still wins: a violation naming a route or query parameter is located from the binding map whatever this says, because POST /employee/{employeeId} carrying a body can reject both the id in the URL and a member of the payload, and one declaration cannot describe both.

It never overwrites either. A violation the producer already located — a route, query or header parameter, or a body pointer — passes through untouched.

The nearest declaration wins. Applied to a controller it covers every action, and an action that disagrees overrides it by declaring its own, because MVC appends action metadata after controller metadata and the endpoint's last declaration is the one read. Minimal APIs get the same precedence from convention order, so a route group's declaration is overridden by an endpoint's.

Only Body, Query and Unspecified may be declared. Route and header values are already settled by evidence, and a declaration nobody can justify is a claim nobody checks.

Derivation gets one case wrong, which is the main reason to reach for an override: a body member sharing a name with a route or query parameter — PUT /employee/{id} carrying {"id": …} — where the URL is evidence about a different value of the same name. Confirming that would mean reflecting over the body type's members, which the AOT-friendly projection path deliberately avoids.

Constructors

InputOriginAttribute(InputLocation)

Initializes a new declaration for the supplied location.

public InputOriginAttribute(InputLocation location)

Parameters

location InputLocation

Where this endpoint's otherwise-unlocated violations came from. Must be Body, Query, or Unspecified to opt out of an enclosing declaration.

Exceptions

ArgumentOutOfRangeException

location is Path, Header, or not a defined InputLocation.

Properties

Location

Gets the location this endpoint declares for violations that arrive without one.

public InputLocation Location { get; }

Property Value

InputLocation