Skip to content

Commit bfe7635

Browse files
refactor: 🔨 Update JsonApiOk function and docs to align with what it actually does
1 parent 95ab6ce commit bfe7635

4 files changed

Lines changed: 30 additions & 33 deletions

File tree

‎CLAUDE.md‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ Documentation is built using DocFX and deployed to GitHub Pages. The documentati
7373
- Query parameters: `QueryParameters`, `FilterParameter`, `SortParameter`
7474
- Errors: `JsonApiError`, `JsonApiErrorResponse`
7575

76+
5. **Attributes** (`Attributes/`)
77+
- `AllowedIncludesAttribute`: Restricts which relationships can be included in responses
78+
79+
6. **Validation** (`Validation/`)
80+
- `IncludePatternValidator`: Validates include patterns with wildcard support
81+
7682
### Key Patterns
7783

7884
- **Convention-based mapping**: Properties are automatically mapped from C# PascalCase to JSON camelCase
@@ -82,6 +88,7 @@ Documentation is built using DocFX and deployed to GitHub Pages. The documentati
8288
- **Filter expressions**: Complex filtering with operators (eq, ne, gt, lt, contains, etc.), logical grouping, and enum support
8389
- **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)
8490
- **Pagination safety**: Invalid page numbers are automatically clamped to valid ranges (page 1 for negative/zero, last page for overflow)
91+
- **Include whitelisting**: Use `AllowedIncludesAttribute` on controller actions to restrict which relationships can be included, preventing unauthorized data exposure
8592

8693
### Service Registration
8794

@@ -124,12 +131,14 @@ Tests are organized by component:
124131

125132
1. **Query Operations**: Extend `FilterExpressionBuilder` and add corresponding operators to `FilterOperator` enum
126133
2. **New Controllers**: Inherit from `JsonApiController` and use provided helper methods
127-
3. **Entity Mapping**: Use `EntityMapper` for custom mapping rules and automatic detection of relationships vs attributes
134+
3. **Entity Mapping**: Use `EntityMapper` for automatic detection of relationships vs attributes based on ID properties
128135
4. **Testing**: Follow existing patterns with xUnit and Moq, test both success and error scenarios
129136

130137
### Common Patterns
131138

132-
- Controllers should inherit from `JsonApiController` and use `JsonApiOkAsync(queryable, "resourceType")`
139+
- Controllers should inherit from `JsonApiController`
140+
- Use `JsonApiOkAsync(queryable, "resourceType")` for collections with full query processing
141+
- Use `JsonApiOk(entity, "resourceType")` for already-loaded entities or collections
133142
- Entity types should have an `Id` property (auto-detected by `EntityMapper.GetIdProperty()`)
134143
- Use `QueryParameters queryParams = GetJsonApiQueryParameters()` to access parsed query parameters
135144
- For manual mapping, use `JsonApiMapper.ToDocument()` or `ToCollectionDocument()`

‎JsonApiToolkit/Controllers/JsonApiController.cs‎

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -57,26 +57,22 @@ protected QueryParameters GetJsonApiQueryParameters()
5757
/// Creates a 200 OK response containing a single resource as a JSON:API document.
5858
/// </summary>
5959
/// <typeparam name="T">The entity type being returned</typeparam>
60-
/// <param name="entity">The entity to serialize into the response</param>
60+
/// <param name="entity">The already-loaded entity to serialize into the response</param>
6161
/// <param name="resourceType">The JSON:API resource type identifier (typically the entity name in camelCase)</param>
6262
/// <returns>An IActionResult with a properly formatted JSON:API document</returns>
6363
/// <remarks>
64-
/// Automatically processes any "include" parameters from the request and adds the specified relationships
65-
/// to the included section of the response.
64+
/// Serializes the provided entity into JSON:API format. Any relationships that are already loaded
65+
/// on the entity will be included in the response.
6666
/// </remarks>
6767
protected IActionResult JsonApiOk<T>(T entity, string resourceType)
6868
where T : class
6969
{
7070
string baseUrl = $"{Request.Scheme}://{Request.Host}{Request.Path}";
71-
QueryParameters queryParams = GetJsonApiQueryParameters();
72-
var mappedIncludes = EfIncludePathHelper.MapIncludePathsToClrProperties<T>(
73-
queryParams.Include
74-
);
7571
JsonApiDocument<ResourceObject> document = JsonApiMapper.ToDocument(
7672
entity,
7773
resourceType,
7874
baseUrl,
79-
mappedIncludes
75+
null // No include filtering - serialize all loaded relationships
8076
);
8177
return Ok(document);
8278
}
@@ -85,14 +81,13 @@ protected IActionResult JsonApiOk<T>(T entity, string resourceType)
8581
/// Creates a 200 OK response containing a collection of resources as a JSON:API document.
8682
/// </summary>
8783
/// <typeparam name="T">The entity type of the collection items</typeparam>
88-
/// <param name="entities">The collection of entities to serialize into the response</param>
84+
/// <param name="entities">The already-loaded collection of entities to serialize into the response</param>
8985
/// <param name="resourceType">The JSON:API resource type identifier (typically the entity name in camelCase)</param>
9086
/// <param name="paginationMeta">Optional pagination metadata to include in the response</param>
9187
/// <returns>An IActionResult with a properly formatted JSON:API collection document</returns>
9288
/// <remarks>
93-
/// Automatically processes any "include" parameters from the request and adds the specified relationships
94-
/// to the included section of the response. When pagination metadata is provided, adds pagination links
95-
/// to the response.
89+
/// Serializes the provided collection into JSON:API format. Any relationships that are already loaded
90+
/// on the entities will be included in the response. When pagination metadata is provided, adds pagination links.
9691
/// </remarks>
9792
protected IActionResult JsonApiOk<T>(
9893
IEnumerable<T> entities,
@@ -102,16 +97,12 @@ protected IActionResult JsonApiOk<T>(
10297
where T : class
10398
{
10499
string baseUrl = GetFullRequestUrl();
105-
QueryParameters queryParams = GetJsonApiQueryParameters();
106-
var mappedIncludes = EfIncludePathHelper.MapIncludePathsToClrProperties<T>(
107-
queryParams.Include
108-
);
109100
JsonApiCollectionDocument<ResourceObject> document = JsonApiMapper.ToCollectionDocument(
110101
entities,
111102
resourceType,
112103
baseUrl,
113104
paginationMeta,
114-
mappedIncludes
105+
null // No include filtering - serialize all loaded relationships
115106
);
116107
return Ok(document);
117108
}
@@ -202,22 +193,19 @@ string resourceType
202193
/// <returns>An IActionResult with Status201Created and a properly formatted JSON:API document</returns>
203194
/// <remarks>
204195
/// Sets the Location header to the resource's URL and includes the resource in the response body.
205-
/// Automatically processes any "include" parameters from the request to add related resources.
196+
/// Serializes the provided entity into JSON:API format. Any relationships that are already loaded
197+
/// on the entity will be included in the response.
206198
/// </remarks>
207199
protected IActionResult JsonApiCreated<T>(T entity, string resourceType, string id)
208200
where T : class
209201
{
210202
string baseUrl = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Request.Path}";
211203
string selfUrl = $"{baseUrl}/{id}";
212-
QueryParameters queryParams = GetJsonApiQueryParameters();
213-
var mappedIncludes = EfIncludePathHelper.MapIncludePathsToClrProperties<T>(
214-
queryParams.Include
215-
);
216204
JsonApiDocument<ResourceObject> document = JsonApiMapper.ToDocument(
217205
entity,
218206
resourceType,
219207
selfUrl,
220-
mappedIncludes
208+
null // No include filtering - serialize all loaded relationships
221209
);
222210
return Created(selfUrl, document);
223211
}

‎docs/docs/api-controller-examples.md‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class BooksController : JsonApiController
3030
[HttpGet]
3131
public async Task<IActionResult> GetBooks()
3232
{
33-
IQueryable<Book> books = _context.Books;
33+
IQueryable<Book> books = _context.Books.AsQueryable();
3434
return await JsonApiOkAsync(books, "book");
3535
}
3636
```
@@ -124,11 +124,11 @@ public async Task<IActionResult> DeleteBook(int id)
124124
public async Task<IActionResult> SearchBooks()
125125
{
126126
// The filtering is handled automatically by JsonApiOkAsync
127-
// But you can also apply custom logic before the standard processing
127+
IQueryable<Book> books = _context.Books.AsQueryable();
128128

129-
var books = _context.Books
130-
.Where(b => b.IsPublished); // Custom business logic
131-
129+
// But you can also apply custom logic before the standard processing
130+
books = books.Where(b => b.IsPublished); // Custom business logic
131+
132132
return await JsonApiOkAsync(books, "book");
133133
}
134134
```
@@ -243,7 +243,7 @@ public async Task<IActionResult> GetPublicOnly()
243243
## Pro Tips
244244

245245
1. **Always use exception types** instead of returning error ActionResults - the filter handles conversion automatically
246-
2. **Use JsonApiOkAsync for collections** - it provides full query parameter support
247-
3. **Use JsonApiOk for single resources** - simpler and faster for individual entities
246+
2. **Use JsonApiOkAsync for collections** when you want automatic query processing (filtering, sorting, pagination)
247+
3. **Use JsonApiOk for already-loaded data** when you've fetched and processed entities yourself
248248
4. **Use AllowedIncludes** - restrict relationship access for security and performance
249249

‎docs/docs/getting-started.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This guide walks you through installing and configuring JsonApiToolkit in your .
55
## Prerequisites
66

77
- [.NET 9.0 SDK](https://dotnet.microsoft.com/download) or later.
8-
- An ASP.NET Core project (typically an API project).
8+
- An ASP.NET Core project.
99

1010
## Installation
1111

0 commit comments

Comments
 (0)