Skip to main content

Fixed: "InvalidCastException: Unable to cast object of type 'System.Attribute[]' to type 'IEnumerable`1[ValidationAttribute]'" with AsParameters in /openapi/v1.json

· 7 min read
Jagdish Kumawat
Founder @ Dewiride

The API itself worked, but /openapi/v1.json started answering 500: InvalidCastException: Unable to cast object of type 'System.Attribute[]' to type 'System.Collections.Generic.IEnumerable1[System.ComponentModel.DataAnnotations.ValidationAttribute]'.The cause was a record used with[AsParameters], with validation attributes on its constructor parameters. It's a bug in ASP.NET Core 10, and adding property:` to each attribute avoids it.

What I Was Doing​

I was adding a product list to a .NET 10 minimal API. Its options come from the query string, so I put them in a record and bound it with [AsParameters], with validation attributes on the page size and sort order:

src/Dewiride.Inventory.Api/Features/Products/ProductContracts.cs (excerpt)
public sealed record ProductListQuery(
string? Search,
int? CategoryId,
bool IncludeArchived = false,
[AllowedValues("name", "-name", "price", "-price", "newest", null)] string? Sort = null,
[Range(1, 1_000_000)] int Page = 1,
[Range(1, 100)] int PageSize = 20);
src/Dewiride.Inventory.Api/Features/Products/ProductEndpoints.cs (excerpt)
private static async Task<Ok<PagedResponse<ProductResponse>>> ListAsync(
[AsParameters] ProductListQuery query,

The endpoint and its validation worked. ?pageSize=1000 answered 400, and a normal request answered 200. Then I opened the OpenAPI document, which MapOpenApi serves in Development:

Request
GET http://localhost:5190/openapi/v1.json

What I Expected​

200 OK, with the JSON description of every endpoint, including the new query parameters.

What I Got​

Response
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json

{"type":"https://tools.ietf.org/html/rfc9110#section-15.6.1","title":"An error occurred while processing your request.","status":500,"traceId":"00-796e7860ee780e91007206353b7f162d-74850200b3154561-00"}
API log (the lines that matter)
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
An unhandled exception has occurred while executing the request.
System.InvalidCastException: Unable to cast object of type 'System.Attribute[]' to type 'System.Collections.Generic.IEnumerable`1[System.ComponentModel.DataAnnotations.ValidationAttribute]'.
at System.Reflection.CustomAttributeExtensions.GetCustomAttributes[T](ParameterInfo element)
at Microsoft.AspNetCore.OpenApi.JsonNodeSchemaExtensions.ApplyParameterInfo(JsonNode schema, ApiParameterDescription parameterDescription, JsonTypeInfo jsonTypeInfo)
at Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.GetOrCreateUnresolvedSchemaAsync(OpenApiDocument document, Type type, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, ApiParameterDescription parameterDescription, CancellationToken cancellationToken)

A terminal where GET /openapi/v1.json answers 500, and the API log shows System.InvalidCastException: Unable to cast object of type 'System.Attribute[]', thrown from JsonNodeSchemaExtensions.ApplyParameterInfo.

What I Checked​

Which change caused it. ApplyParameterInfo is in the stack trace, so it was about a parameter's attributes. The only new parameters with attributes were the ones in ProductListQuery. The body records with validation attributes, such as CreateProductRequest, were fine.

Whether it's known. It is: dotnet/aspnetcore issue #65348, "InvalidCastException when using [AsParameters] with primary constructor record that has ValidationAttribute on constructor parameters". It has the same stack trace, and a four-line way to reproduce it.

Where the bug is. A comment on that issue points at PropertyAsParameterInfo, the class that presents a record's property as a parameter. When a constructor parameter has attributes, the ASP.NET Core 10 source merges them into an array of the wrong type:

PropertyAsParameterInfo.cs (release/10.0)
var mergedAttributes = new Attribute[constructorAttributes.Length + propertyAttributes.Length];

GetCustomAttributes<T> then tries to treat that Attribute[] as a list of ValidationAttribute, and the cast fails.

Whether it's fixed. Pull request #67284 fixed it for 11.0-preview7. The release/10.0 branch above still has the old code, and this API runs on 10.0.14, because .NET 10 is the newest generally available release.

Why It Happens​

In a record's primary constructor, an attribute goes on the constructor parameter by default, not on the property the compiler generates. The code above shows the bug needs constructor attributes: with none, GetCustomAttributes returns the property's attributes directly, in an array of the right type. So moving the attributes to the property avoids the broken path.

The Fix​

Target the property, with property: in front of each attribute:

src/Dewiride.Inventory.Api/Features/Products/ProductContracts.cs (excerpt)
public sealed record ProductListQuery(
string? Search,
int? CategoryId,
bool IncludeArchived = false,
[property: AllowedValues("name", "-name", "price", "-price", "newest", null, ErrorMessage = "Sort must be name, -name, price, -price or newest.")]
string? Sort = null,
[property: Range(1, 1_000_000)] int Page = 1,
[property: Range(1, 100)] int PageSize = 20);

Only records used with [AsParameters] need this. Records bound from the JSON body can keep their attributes as they are.

After the Fix​

Response (GET /openapi/v1.json, the paths)
200 OK
/api/v1/categories post, get
/api/v1/categories/{id} get, put, delete
/api/v1/products post, get
/api/v1/products/{id} get, put
/api/v1/products/{id}/archive post
/api/v1/products/{id}/restore post

And validation still works, which was the thing to check:

Response (GET /api/v1/products?pageSize=1000)
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": {
"PageSize": [
"The field PageSize must be between 1 and 100."
]
},
"traceId": "00-d83d3bde5633b55ce46b08a481e27c3c-71fb45f1a5726033-00"
}

A terminal where GET /openapi/v1.json answers 200 with the API's paths, and GET /api/v1/products?pageSize=1000 still answers 400 with the message that PageSize must be between 1 and 100.

Common Mistakes​

MistakeWhat happensDo this instead
Only testing the endpoints themselvesThe document can be broken while the API worksOpen /openapi/v1.json after adding parameters
Removing the validation attributes to make the error go awayClients can ask for a million rowsUse property:
Switching to Swagger or another generator for thisA lot of change for one prefixUse property:
Assuming property: turns validation offIt doesn't: it still answered 400 hereTest one bad value to be sure

Troubleshooting​

SymptomLikely causeFix
/openapi/v1.json answers 500 with this InvalidCastExceptionValidation attributes on the constructor parameters of an [AsParameters] recordAdd property:
Scalar or Swagger UI shows an error or an empty pageThe document they read answers 500The same; check the JSON address directly
The cast error names DescriptionAttributeThe same bug, with another attribute on an [AsParameters] recordThe same
Validation stops workingThe attribute targets something else, such as param:Use property:

FAQ​

Do I need this on .NET 11?​

The fix was merged for 11.0-preview7, so it shouldn't be needed there. property: keeps working anyway.

Why does a body record not need it?​

The bug is in PropertyAsParameterInfo, which only [AsParameters] uses.

Conclusion​

Unable to cast object of type 'System.Attribute[]' in /openapi/v1.json comes from a bug in ASP.NET Core 10, triggered by validation attributes on the constructor parameters of an [AsParameters] record. Add property: to those attributes. The document generates again, and validation still runs.

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.