dotnet / dotnet/aspnetcore

[MinimalAPI] Resolve JsonSourceGenerationOptions for build-in parameter binding and response

Open
#53,191 1 comment 0 reactions 0 assignees View on GitHub
area-minimal
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Is your feature request related to a problem? Please describe the problem.

Related issues:
- #52088
- #52642
- #53189

There has been discussion in the above-mentioned issue. But it seems like we haven't reached a conclusion.
For now we can only add the `TypeInfoResolver` into `TypeInfoResolverChain`.
And the `SerializerOptions` can only be set globally.
What if we want to specify the `SerializerOptions` based on `Type` or `JsonSerializerContext` as a group configuration?
There should be the flexibility that different `Type`s can be [de]serialized with different options.

Also, from first impression, the following code should apply the `JsonSourceGenerationOptions` for all related part.
```csharp
builder.Services.ConfigureHttpJsonOptions(
options => { options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); });
```

### Parameter Binding
The `JsonSourceGenerationOptions` is not resolved when build-in parameter binding which will result in binding failure:
```csharp
//request json object uses options same as JsonSourceGenerationOptions
//for now, asp.net core always provide BadRequest since it does not use JsonSourceGenerationOptions. The binding failed.
app.MapPost("/todos", ([FromBody] Todo todo)) => Handle(todo))
```
There are solutions, but they introduce verbose:
```csharp
//Manually parse from request
app.MapPost("/todos", (HttpRequest httpRequest) =>
{
request = await httpRequest.ReadFromJsonAsync(AppJsonSerializerContext.Default.Todo);
//manually handle bad request. e.g., return TypedResults.BadRequest() when deserializing failed`
}

//Implement custom binding
public record Todo(int Id, Color Color, string? Title, DateOnly? DueBy = null, bool IsComplete = false)
{
public static async ValueTask BindAsync(HttpContext context)
=> await context.Request.ReadFromJsonAsync(AppJsonSerializerContext.Default.Todo);
}
```

### Response
The response also does not apply the `JsonSourceGenerationOptions`:
The output json just uses global `SerializerOptions`. If we want to apply the `JsonSourceGenerationOptions` we have to use overloads manually which introduces verbose.
```csharp
//issue 53189 reports that not all necessary methods have overloads like this
app.MapGet("/", () => TypedResults.Json(todo, AppJsonSerializerContext.Default.Todo))
```

### Code Snippet
```csharp
public record Todo(int Id, Color Color, string? Title, DateOnly? DueBy = null, bool IsComplete = false);

public enum Color
{
Red,
Blue,
Orange
}

[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
Converters =
[
typeof(CamelCaseJsonStringEnumConverter)
])]
[JsonSerializable(typeof(Color))]
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext;

public class CamelCaseJsonStringEnumConverter() : JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
where TEnum : struct, Enum;
```

### Describe the solution you'd like

When we configured like:
```csharp
builder.Services.ConfigureHttpJsonOptions(
options => { options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); });
```
`JsonSourceGenerationOptions` should be build-in resolved, and we can avoid above mentioned verbose.

If that's not enough, add a centralized place to configure the `SerializerOptions` based on `Type` | `JsonSerializerContext` | Group of `Type`s.

### Additional context

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.