-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathProgram.cs
More file actions
84 lines (72 loc) · 3.49 KB
/
Copy pathProgram.cs
File metadata and controls
84 lines (72 loc) · 3.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
using Popcorn;
using System.Text.Json.Serialization;
using over;
using Popcorn.Shared;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddHttpContextAccessor();
builder.Services.AddPopcorn(o =>
{
// Route error responses through the custom envelope shape declared below.
// This is the AOT canary for the custom-envelope code path: generator emits the writer,
// AddPopcornEnvelopes installs it at DI time (no reflection needed at runtime), and
// UsePopcornExceptionHandler calls it on any unhandled exception.
o.EnvelopeType = typeof(AotCustomEnvelope<>);
});
builder.Services.AddPopcornEnvelopes();
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.PropertyNamingPolicy = null;// JsonNamingPolicy.KebabCaseUpper;
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
options.SerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
options.SerializerOptions.AddPopcornOptions();
});
var app = builder.Build();
app.UsePopcornExceptionHandler();
app.MapGet("/todos", ([FromServices] IPopcornAccessor contextAccess) => contextAccess.CreateResponse(new List<over.Todo?> {
new over.Todo(1, null, "Hello World", DateTimeOffset.Now, false),
new over.Todo(2, new under.SubTodo(1, 2, 3), null, null, true)
}));
app.MapGet("/null", ([FromServices] IPopcornAccessor contextAccess) => contextAccess.CreateResponse<over.Todo?>(null));
app.MapGet("/sub", ([FromServices] IPopcornAccessor contextAccess) => contextAccess.CreateResponse(new over.Todo(1, new under.SubTodo(1, 2, 3), "Hello World", DateTimeOffset.Now, false)));
// Smoke endpoint: throws to exercise the custom error envelope through the AOT pipeline.
app.MapGet("/boom", () =>
{
throw new InvalidOperationException("aot boom");
#pragma warning disable CS0162
return Results.Ok();
#pragma warning restore CS0162
});
app.Run();
namespace under
{
public record SubTodo(int i, int j, int k);
}
namespace over
{
public record Todo([property: Always] int Id, [property: Default] under.SubTodo? ToDo, [property: Default] string? Title, [property: JsonPropertyName("DueDate")] DateTimeOffset? DueBy = null, [property: Never] bool IsComplete = false);
}
// Custom response envelope for the AOT example. Marker attributes let the generator emit a
// reflection-free error writer that the exception middleware dispatches through
// PopcornErrorWriterRegistry.
[PopcornEnvelope]
public record class AotCustomEnvelope<T>
{
[PopcornSuccess] public bool Ok { get; init; } = true;
[PopcornPayload] public Pop<T> Payload { get; init; }
[PopcornError] public ApiError? Problem { get; init; }
}
/*
* For well-defined object graphs, including only the top-level types in [JsonSerializable(typeof(ApiResponse<?>))] attributes is sufficient
* because the metadata generator will traverse the object graph and include nested types.
* For dynamic or polymorphic scenarios, explicitly list all possible runtime types.
* Use tools like the ILLink analyzer to catch missing metadata during development.
* If in doubt, be more explicit with [JsonSerializable] attributes to ensure all required types are covered.
*/
[JsonSerializable(typeof(ApiResponse<List<Todo?>>))]
[JsonSerializable(typeof(ApiResponse<Todo>))]
[JsonSerializable(typeof(AotCustomEnvelope<List<Todo?>>))]
[JsonSerializable(typeof(AotCustomEnvelope<Todo>))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}