Popcorn is a .Net Middleware for your RESTful API that allows your consumers to request exactly as much or as little as they need, with no effort from you.
This project is maintained by Skyward App Company
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=TrueandPublishTrimmed=True. If you’re on the v7Skyward.Api.Popcorn/.DotNetCorepackages and aren’t ready to migrate, the v7 quick start is in the migration guide.
<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.
JsonSerializerContextPopcorn’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 { }
Program.csusing 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.
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" }, ...] }
?include=
grammar (negation, wildcards, nesting).[Default] / [Always] /
[SubPropertyDefault] attribute usage.System.Text.Json and v7.dotnet/PopcornAotExample/ for a reference project that
publishes with PublishAot=True + PublishTrimmed=True.