Popcorn > Documentation > DotNet > Tutorial: Default Includes
If you're new to Popcorn, start with Getting Started —
this tutorial assumes you already have a working app and understand the basics of ?include=.
Popcorn lets you declare which fields show up when the client makes a "bare" request — one
with no ?include= parameter or with an empty ?include=[]. You do this with three
attributes on the model, all in the Popcorn namespace.
| Attribute | Emitted when |
|---|---|
[Always] |
Every response, regardless of ?include=. Cannot be negated. |
[Default] |
?include= is absent, empty, or !default. Can be negated via -FieldName. |
[Never] |
Never. Not even when ?include=[FieldName] asks for it explicitly. |
Suppose we extend the Employee model from Getting Started
with a computed FullName property:
using Popcorn;
public class Employee
{
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";
public string FullName => $"{FirstName} {LastName}";
public DateTimeOffset Birthday { get; set; }
public int VacationDays { get; set; }
public List<Car> Vehicles { get; set; } = new();
}A bare GET /employees request would return every public property. For most APIs that's too
much — the client ends up paying for data it won't render. Let's declare a smaller default.
public class Employee
{
[Default] public string FirstName { get; set; } = "";
[Default] public string LastName { get; set; } = "";
public string FullName => $"{FirstName} {LastName}";
public DateTimeOffset Birthday { get; set; }
public int VacationDays { get; set; }
public List<Car> Vehicles { get; set; } = new();
}GET /employees
{
"Success": true,
"Data": [
{ "FirstName": "Liz", "LastName": "Lemon" },
{ "FirstName": "Jack", "LastName": "Donaghy" }
]
}The Birthday, VacationDays, FullName, and Vehicles fields are still available on the
wire — clients just have to ask for them:
GET /employees?include=[FirstName,Birthday,FullName]
{
"Success": true,
"Data": [
{ "FirstName": "Liz", "Birthday": "1981-05-01T00:00:00+00:00", "FullName": "Liz Lemon" },
{ "FirstName": "Jack", "Birthday": "1957-07-12T00:00:00+00:00", "FullName": "Jack Donaghy" }
]
}If a type has no [Default] or [Always] attributes anywhere, every property is treated
as default-included. This keeps getting-started friction low — you can start emitting everything
and only introduce attributes when you want to tighten the default set.
| Attribute layout | Default set |
|---|---|
No [Default] / [Always] anywhere on the type |
All properties |
At least one [Default] |
Only properties marked [Default] or [Always] |
[Never] on a property |
That property is excluded, always, regardless of other rules |
Inheritance: [Default] / [Always] on a base class flow through to derived classes. [Never]
does the same. You don't need to re-declare them on the subclass.
Some fields — primary keys, tenant IDs, version columns — should always be present even if the
client forgot to ask. [Always] guarantees that:
public class Employee
{
[Always] public int Id { get; set; }
[Default] public string FirstName { get; set; } = "";
[Default] public string LastName { get; set; } = "";
...
}GET /employees?include=[FirstName]
{
"Success": true,
"Data": [
{ "Id": 1, "FirstName": "Liz" },
{ "Id": 2, "FirstName": "Jack" }
]
}Id shows up even though the client didn't list it. Negation won't remove it either — a
request for ?include=[!all,-Id] still emits Id. If a field is genuinely sensitive, use
[Never], not [Always] — see Internal-Only Fields.
When a property is a complex type (like List<Car>), including the parent without specifying
sub-children normally falls back to the child type's own default set. [SubPropertyDefault]
lets you override that decision for this property:
public class Employee
{
[Default] public string FirstName { get; set; } = "";
[Default] public string LastName { get; set; } = "";
[SubPropertyDefault("[Make,Model,Color]")]
public List<Car> Vehicles { get; set; } = new();
}GET /employees?include=[FirstName,Vehicles]
{
"Success": true,
"Data": [
{
"FirstName": "Liz",
"Vehicles": [
{ "Make": "Pontiac", "Model": "Firebird", "Color": 2 }
]
},
...
]
}Without the attribute, Vehicles would have emitted the Car type's own default set. The
override applies only when the client doesn't spell out sub-children: ?include=[Vehicles[Year]]
still wins (explicit sub-children beat the attribute), and [Never] on a Car property still
wins over the attribute (declared "never-emit" always beats "default-emit").
The include string is parsed once per process into a static readonly field at generation time — no per-request parsing cost.
?include=[!default] is shorthand for "the default set" — useful when you want the default
set plus a couple of extra fields:
GET /employees?include=[!default,Birthday]
→ default set (FirstName + LastName) plus Birthday
GET /employees?include=[!default,-LastName]
→ default set minus LastName (FirstName only)
[Always]-marked fields are still included regardless of negation — -Id is a silent no-op
if Id is marked [Always].
- Include Parameter Syntax for the full grammar
(nesting, negation,
!all). - Internal-Only Fields for
[Never]. - Wildcard Includes for
!all.