Popcorn

Partial-response JSON for ASP.NET Core: API clients ask for exactly the fields they need. v8 is a Roslyn source generator for System.Text.Json and Native AOT.

This project is maintained by Skyward App Company

Contents
Latest Post

Popcorn > Quick Start > DotNet

Table Of Contents

Drop Popcorn v8 into a minimal-API app in five minutes. For a full walkthrough (models, test data, multiple endpoints) see Getting Started.

v7 vs v8. This is the v8 (source-generator) path — it works under PublishAot=True and PublishTrimmed=True. If you’re on the v7 Skyward.Api.Popcorn / .DotNetCore packages and aren’t ready to migrate, the v7 quick start is in the migration guide.

1. Install the packages

<PackageReference Include="Skyward.Api.Popcorn.SourceGen.Shared" Version="8.0.0-preview.1" />
<PackageReference Include="Skyward.Api.Popcorn.SourceGen"        Version="8.0.0-preview.1" PrivateAssets="all" />

SourceGen is marked developmentDependency — it only contributes the Roslyn analyzer, never a runtime DLL. SourceGen.Shared is the runtime library that carries attributes, envelopes, and middleware.

Build requirement. .NET SDK 9.0.100 or later is required to build with the source generator (Roslyn 4.12); .NET 10 SDK recommended. In Visual Studio that means 2022 17.12 or later. The requirement is on the SDK that compiles your project, not on its target framework: a net8.0 app builds fine with the .NET 10 SDK. On an older SDK (the .NET 8 SDKs ship Roslyn 4.8–4.11) the compiler skips the generator with warning CS9057 (“The analyzer assembly … references version ‘4.12.0.0’ of the compiler, which is newer than the currently running version …”), and the build then fails with errors about missing generated members such as AddPopcornOptions. Check with dotnet --version, or pin the SDK with a global.json.

2. Declare a JsonSerializerContext

Popcorn’s generator discovers types through standard System.Text.Json [JsonSerializable] attributes. Register each top-level response type — the generator walks nested types automatically.

using System.Text.Json.Serialization;
using Popcorn.Shared;

[JsonSerializable(typeof(ApiResponse<List<Car>>))]
[JsonSerializable(typeof(ApiResponse<Car>))]
internal partial class AppJsonSerializerContext : JsonSerializerContext { }

3. Wire up Program.cs

using System.Text.Json;
using Popcorn.Shared;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.AddHttpContextAccessor();
builder.Services.AddPopcorn();

builder.Services.ConfigureHttpJsonOptions(o =>
{
    o.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
    o.SerializerOptions.AddPopcornOptions(); // installs generator-emitted converters
});

var app = builder.Build();
app.UsePopcornExceptionHandler(); // unhandled exceptions → ApiError envelope

app.MapGet("/cars", (IPopcornAccessor access) =>
    access.CreateResponse(new List<Car>
    {
        new(1, "Pontiac", "Firebird", 1981),
        new(2, "Porsche", "Cayman",    2005),
    }));

app.Run();

public record Car(int Id, string Make, string Model, int Year);

CreateSlimBuilder is the AOT-friendly host builder; if you don’t plan to publish as AOT you can use WebApplication.CreateBuilder(args) instead.

4. Make a request

GET /cars
→ { "Success": true, "Data": [{ "Id":1, "Make":"Pontiac", "Model":"Firebird", "Year":1981 }, ...] }

GET /cars?include=[Make,Model]
→ { "Success": true, "Data": [{ "Make":"Pontiac", "Model":"Firebird" }, ...] }

Where to go next

  • Getting Started for a fuller walkthrough with multiple models, default-include attributes, and nested sub-entity queries.
  • Include Parameter Syntax for the full ?include= grammar (negation, wildcards, nesting).
  • Default Includes for [Default] / [Always] / [SubPropertyDefault] attribute usage.
  • Performance for benchmarked ratios vs raw System.Text.Json and v7.
  • dotnet/PopcornAotExample/ for a reference project that publishes with PublishAot=True + PublishTrimmed=True.