Repository navigation
Description
Problem
Decision 39 left a rejected value-object input with no fault in the response. A refused route or query value is an empty 400: TryParse reports only false, so the fault is lost (spikes/ValidationFaults, finding 5). A refused minimal-API body reached a hook only with ThrowOnBadRequest for the whole host. The decision is to be reopened "if ASP.NET hands a binding failure to a hook without ThrowOnBadRequest".
ASP.NET Core now does that. When a minimal-API endpoint has a filter, the filter pipeline runs even if binding fails. The filter sees HttpContext.Response.StatusCode == 400 and may write its own response (What's new in ASP.NET Core 11, dotnet/aspnetcore#64539). The change was backported to release/10.0 (dotnet/aspnetcore#66221).
The filter doesn't get which parameter failed, the exception or the fault. A filter that only sees the 400 adds nothing over UseStatusCodePages() (finding 2).
Change
A spike first, under spikes/, with a README giving the question, the answer and the evidence. It decides:
- Whether the fault can be recovered without changing the base package. The candidate is an opt-in endpoint filter, on a route or a group. On a
400it reads the raw route and query value of each value-object parameter again, parses the wrapped type, runs the value object'sCreate, and reports the fault as problem details keyed by the parameter's name. It uses fault identifiers only, since no refusal quotes the input. - Which positions it reaches: a route, query and header value, a missing body, a malformed body, and a body whose value object refuses. Reading the source suggests that a malformed body returns before the filter runs; measure it.
- The hosts:
ThrowOnBadRequestdefaults to true inDevelopment, where the framework throws instead. Also the Request Delegate Generator and Native AOT. MVC is out of scope (finding 4). - The target: which 10.0.x servicing release ships the backport, so whether this ships on
net10.0or onnet11.0only (decision 120). - The cost: parsing again only on the failure path, never on a request that bound.
Then a decision record that supersedes decision 39 states the answer: the filter lands in 2.4.0, or the record says why not.
Done when
- The spike's README answers the questions above with measurements, and the framework facts it turns on are
design/knowledge/entries (decision 93). - A decision record supersedes 0039, and the ADR index says so.
- If it lands, it has:
- a test per position on each target it ships on;
- an
UnreadableInputTestsrow; - the AspNetCore README section and the changelog line;
- claims in
test/oracles/claims.json.
Activity