Skip to content

Commit 903eda3

Browse files
fix: 🚑️ JsonApiOk and JsonApiCreated methods not adding includes
1 parent 55933b7 commit 903eda3

2 files changed

Lines changed: 25 additions & 5 deletions

File tree

CLAUDE.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Documentation is built using DocFX and deployed to GitHub Pages. The documentati
5656
- Base controller for JSON:API endpoints
5757
- Provides methods: `JsonApiOk()`, `JsonApiQueryAsync()`, `JsonApiCreated()`, `JsonApiNotFound()`, etc.
5858
- Handles query parameter parsing and response formatting
59-
- Automatically applies filtering, sorting, pagination, and includes (filtering applies to main entity; includes load related resources)
59+
- Automatically applies filtering, sorting, pagination, and includes (filtering applies to main entity and included resources; includes load related resources)
6060

6161
2. **JsonApiMapper** (`Mapping/JsonApiMapper.cs`)
6262
- Core mapper for converting entities to JSON:API resource structures
@@ -80,13 +80,18 @@ Documentation is built using DocFX and deployed to GitHub Pages. The documentati
8080
6. **Validation** (`Validation/`)
8181
- `IncludePatternValidator`: Validates include patterns with wildcard support
8282

83+
7. **Include Filtering** (`Extensions/Querying/`)
84+
- `IncludeFilterParser`: Separates filters targeting included resources from main entity filters
85+
- `FilteredIncludeBuilder`: Applies filtered includes using EF Core's filtered Include functionality
86+
- Enables filtering on relationships (e.g., `filter[author.name]=John` with `include=author`)
87+
8388
### Key Patterns
8489

8590
- **Convention-based mapping**: Properties are automatically mapped from C# PascalCase to JSON camelCase
8691
- **Query parameter parsing**: Standard JSON:API query syntax (`filter[field]=value`, `sort=field,-field2`, `page[number]=1&page[size]=10`, `include=relationship`)
8792
- **Async-first**: Main controller method `JsonApiQueryAsync()` is async and works with `IQueryable<T>`
8893
- **Entity Framework integration**: Uses EF Core's `Include()` and query building capabilities
89-
- **Filter expressions**: Complex filtering with operators (eq, ne, gt, lt, contains, etc.), logical grouping, and enum support
94+
- **Filter expressions**: Complex filtering with operators (eq, ne, gt, lt, contains, etc.), logical grouping, enum support, and filtering on included resources
9095
- **JSON column detection**: Collections and complex objects without ID properties are automatically mapped as JSON attributes instead of relationships (useful for EF Core owned entities stored as JSON columns)
9196
- **Pagination safety**: Invalid page numbers are automatically clamped to valid ranges (page 1 for negative/zero, last page for overflow)
9297
- **Include whitelisting**: Use `AllowedIncludesAttribute` on controller actions to restrict which relationships can be included, preventing unauthorized data exposure

JsonApiToolkit/Controllers/JsonApiController.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,17 @@ protected QueryParameters GetJsonApiQueryParameters()
6767
protected IActionResult JsonApiOk<T>(T entity, string resourceType)
6868
where T : class
6969
{
70+
QueryParameters parameters = GetJsonApiQueryParameters();
71+
var mappedIncludes = EfIncludePathHelper.MapIncludePathsToClrProperties<T>(
72+
parameters.Include
73+
);
74+
7075
string baseUrl = $"{Request.Scheme}://{Request.Host}{Request.Path}";
7176
JsonApiDocument<ResourceObject> document = JsonApiMapper.ToDocument(
7277
entity,
7378
resourceType,
7479
baseUrl,
75-
null // No include filtering - serialize all loaded relationships
80+
mappedIncludes
7681
);
7782
return Ok(document);
7883
}
@@ -96,13 +101,18 @@ protected IActionResult JsonApiOk<T>(
96101
)
97102
where T : class
98103
{
104+
QueryParameters parameters = GetJsonApiQueryParameters();
105+
var mappedIncludes = EfIncludePathHelper.MapIncludePathsToClrProperties<T>(
106+
parameters.Include
107+
);
108+
99109
string baseUrl = GetFullRequestUrl();
100110
JsonApiCollectionDocument<ResourceObject> document = JsonApiMapper.ToCollectionDocument(
101111
entities,
102112
resourceType,
103113
baseUrl,
104114
paginationMeta,
105-
null // No include filtering - serialize all loaded relationships
115+
mappedIncludes
106116
);
107117
return Ok(document);
108118
}
@@ -216,13 +226,18 @@ string resourceType
216226
protected IActionResult JsonApiCreated<T>(T entity, string resourceType, string id)
217227
where T : class
218228
{
229+
QueryParameters parameters = GetJsonApiQueryParameters();
230+
var mappedIncludes = EfIncludePathHelper.MapIncludePathsToClrProperties<T>(
231+
parameters.Include
232+
);
233+
219234
string baseUrl = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Request.Path}";
220235
string selfUrl = $"{baseUrl}/{id}";
221236
JsonApiDocument<ResourceObject> document = JsonApiMapper.ToDocument(
222237
entity,
223238
resourceType,
224239
selfUrl,
225-
null // No include filtering - serialize all loaded relationships
240+
mappedIncludes
226241
);
227242
return Created(selfUrl, document);
228243
}

0 commit comments

Comments
 (0)