Skip to content

Commit d22466d

Browse files
Merge pull request #35 from intility/34-jsonapiokasync
feat: ✨ `JsonApiOkAsync`
2 parents af12b0b + bc26940 commit d22466d

1 file changed

Lines changed: 61 additions & 8 deletions

File tree

JsonApiToolkit/Controllers/JsonApiController.cs

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,57 @@ protected IActionResult JsonApiOk<T>(T entity, string resourceType)
9191
return Ok(document);
9292
}
9393

94+
/// <summary>
95+
/// Returns 200 OK for a single resource queryable with JSON:API query support (filter, include).
96+
/// Use this when you need includes to be automatically loaded from the database.
97+
/// </summary>
98+
protected async Task<IActionResult> JsonApiOkAsync<T>(
99+
IQueryable<T> queryable,
100+
string resourceType
101+
)
102+
where T : class
103+
{
104+
QueryParameters parameters = GetJsonApiQueryParameters();
105+
106+
var mappedIncludes = EfIncludePathHelper.MapIncludePathsToClrProperties<T>(
107+
parameters.Include
108+
);
109+
110+
var (mainFilters, includeFilters) = IncludeFilterParser.SeparateIncludeFilters(
111+
parameters.Filter,
112+
parameters.Include
113+
);
114+
115+
IQueryable<T> filteredQuery = queryable;
116+
117+
// Apply main entity filters
118+
if (mainFilters != null)
119+
filteredQuery = filteredQuery.ApplyFilters(mainFilters, Logger);
120+
121+
// Apply includes (with or without filters)
122+
if (includeFilters.Count > 0)
123+
{
124+
filteredQuery = filteredQuery.ApplyFilteredIncludes(
125+
mappedIncludes,
126+
includeFilters,
127+
Logger
128+
);
129+
}
130+
else if (mappedIncludes.Count > 0)
131+
{
132+
filteredQuery = filteredQuery.ApplyIncludes(mappedIncludes);
133+
}
134+
135+
// Execute query for single entity
136+
T? entity = await filteredQuery.FirstOrDefaultAsync().ConfigureAwait(false);
137+
138+
if (entity == null)
139+
return JsonApiNotFound();
140+
141+
// Use existing JsonApiOk - entity now has includes loaded
142+
return JsonApiOk(entity, resourceType);
143+
}
144+
94145
/// <summary>
95146
/// Returns 200 OK with a collection of resources as JSON:API document.
96147
/// </summary>
@@ -178,14 +229,19 @@ string resourceType
178229
includeFilters.Count,
179230
typeof(T).Name
180231
);
181-
filteredQuery = filteredQuery.ApplyFilteredIncludes(mappedIncludes, includeFilters, Logger);
232+
filteredQuery = filteredQuery.ApplyFilteredIncludes(
233+
mappedIncludes,
234+
includeFilters,
235+
Logger
236+
);
182237
}
183238
else if (mappedIncludes.Count > 0)
184239
{
185240
// Use single query with pagination to avoid EF Core split query issues
186-
filteredQuery = parameters.Pagination != null
187-
? filteredQuery.ApplyIncludesSingleQuery(mappedIncludes)
188-
: filteredQuery.ApplyIncludes(mappedIncludes);
241+
filteredQuery =
242+
parameters.Pagination != null
243+
? filteredQuery.ApplyIncludesSingleQuery(mappedIncludes)
244+
: filteredQuery.ApplyIncludes(mappedIncludes);
189245

190246
Logger.LogDebug(
191247
"Applied {IncludeCount} includes for {EntityType} using {QueryType}",
@@ -202,10 +258,7 @@ string resourceType
202258

203259
if (totalCount == 0 && parameters.Filter?.Filters?.Count > 0)
204260
{
205-
Logger.LogInformation(
206-
"Query returned 0 results for {EntityType}",
207-
typeof(T).Name
208-
);
261+
Logger.LogInformation("Query returned 0 results for {EntityType}", typeof(T).Name);
209262
}
210263
else if (totalCount > 1000 && parameters.Pagination == null)
211264
{

0 commit comments

Comments
 (0)