Skip to main content

Fixed: Minimal API Accepts an Undefined Enum Value in the Query String

· 4 min read
Jagdish Kumawat
Founder @ Dewiride

GET /orders?status=99 didn't fail. My enum has three members, and the endpoint happily ran with status 99, which then filters to nothing, with no sign that the request was wrong. .NET's enum parsing allows this on purpose, so the check is yours to add.

What I Was Doing​

My .NET C# Web API on ASP.NET Core 10 has a query parameter of an enum type, with three members:

An endpoint
g.MapGet("/enum", (OrderStatus? status) => Results.Ok(new { status }));
Request
GET http://localhost:5190/demo/enum?status=99

What I Expected​

A 400, as for any other value that isn't a status.

What I Got​

Response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"status": 99
}

A response of 200 OK with status set to the number 99.

What I Checked​

A number that is a member.

Response (?status=1)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"status": "Fulfilled"
}

So numbers are accepted at all, whether or not they mean anything.

The docs. Enum.TryParse says: "If value is the string representation of an integer that does not represent an underlying value of the TEnum enumeration, the method returns an enumeration member whose underlying value is value converted to an integral type. If this behavior is undesirable, call the IsDefined method to ensure that a particular string representation of an integer is actually a member of TEnum."

Why It Happens​

An enum is a number with names on some of its values, and any number of the right type fits. Parsing succeeds, so binding succeeds, and nothing else checks it.

The Fix​

Move the query string into a record with [AsParameters], and add EnumDataType, which AddValidation() checks. The property: target matters on an [AsParameters] record, because an attribute on the parameter itself breaks /openapi/v1.json:

src/Dewiride.Inventory.Api/Features/Orders/OrderContracts.cs (excerpt)
/// <summary>The filter and page of the order list, read from the query string.</summary>
public sealed record OrderListQuery(
[property: EnumDataType(typeof(OrderStatus))] OrderStatus? Status = null,
[property: Range(1, 1_000_000)] int Page = 1,
[property: Range(1, 100)] int PageSize = 20);

After the Fix​

Response (?status=99)
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Status": [
"The field Status is invalid."
]
},
"traceId": "00-eab336a5322945321958030bff8e26a2-fc43141c3907b294-00"
}
Response (?status=Placed)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"status": "Placed"
}
Response (no status)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"status": null
}

A response of 400 for status=99 with The field Status is invalid, and 200 for status=Placed.

A real member still works, and leaving it out still means no filter.

Common Mistakes​

MistakeWhat happensDo this instead
Assuming a typed parameter means a valid value99 gets throughValidate
Putting [EnumDataType] on the record's parameter without property:The OpenAPI document breaksUse [property: ...]
Checking only Enum.TryParseNumbers pass itAdd Enum.IsDefined

Troubleshooting​

SymptomLikely causeFix
99 still bindsAddValidation() is missing, or there's no [AsParameters]Add them
?status=placed gives a 400Query strings use the exact nameThe fix
A JSON body accepts 99 tooThe converter allows numbersnew JsonStringEnumConverter(allowIntegerValues: false)

Conclusion​

.NET parses any number into any enum, and minimal API binding uses that parsing. Add EnumDataType to a parameter record, or check Enum.IsDefined, to refuse the values that aren't members.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.