Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.RegularExpressions;
using Dappi.HeadlessCms.Models;
Expand Down
1 change: 0 additions & 1 deletion Dappi.HeadlessCms.Tests/TestData/PropertyAndClassNames.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Dappi.HeadlessCms.Models;
using Xunit;

namespace Dappi.HeadlessCms.Tests.TestData
{
Expand Down
64 changes: 64 additions & 0 deletions Dappi.HeadlessCms/ActionFilters/IncludeQueryFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Dappi.HeadlessCms.Models;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Dappi.HeadlessCms.ActionFilters
{
public class IncludeQueryFilter : ActionFilterAttribute
{
public const string IncludeParamsKey = "Includes";

public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.HttpContext.Request.Query.TryGetValue("include", out var includeValues))
return;

var includeTree = new Dictionary<string, IncludeNode>(StringComparer.OrdinalIgnoreCase);

foreach (var segments in from includeValue in includeValues.OfType<string>() select includeValue
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) into includePaths from includePath in includePaths select includePath
.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToArray() into segments where segments.Length != 0 select segments)
{
AddSegments(includeTree, segments, 0);
}

if (includeTree.Count == 0)
{
return;
}

context.HttpContext.Items[IncludeParamsKey] = includeTree;
}

private static void AddSegments(IDictionary<string, IncludeNode> nodes, IReadOnlyList<string> segments, int index)
{
while (index != segments.Count)
{
var segment = CapitalizeSegment(segments[index]);
if (!nodes.TryGetValue(segment, out var current))
{
current = new IncludeNode(segment);
nodes[segment] = current;
}

nodes = current.Children;
index++;
}
}

private static string CapitalizeSegment(string segment)
{
if (string.IsNullOrEmpty(segment))
{
return segment;
}

if (segment.Length == 1)
{
return segment.ToUpperInvariant();
}

return char.ToUpperInvariant(segment[0]) + segment.Substring(1);
}
}
}
14 changes: 14 additions & 0 deletions Dappi.HeadlessCms/Models/IncludeNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Dappi.HeadlessCms.Models
{
public class IncludeNode
{
public IncludeNode(string name)
{
Name = name;
}

public string Name { get; }

public IDictionary<string, IncludeNode> Children { get; } = new Dictionary<string, IncludeNode>(StringComparer.OrdinalIgnoreCase);
}
}
30 changes: 30 additions & 0 deletions Dappi.SourceGenerator/CrudGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ protected override void Execute(SourceProductionContext context,
using System.IO;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using Dappi.Core.Constants;
using System.Globalization;
using System.Linq;
Expand Down Expand Up @@ -136,6 +137,35 @@ private dynamic GetDbSetForType(string typeName)

return dbSetProperty?.GetValue(dbContext);
}}

private IQueryable<{item.ClassName}> ApplyDynamicIncludes(IQueryable<{item.ClassName}> query)
{{
var includeTree = HttpContext.Items[IncludeQueryFilter.IncludeParamsKey] as IDictionary<string, IncludeNode>;
if (includeTree is null || includeTree.Count == 0)
{{
return query;
}}

foreach (var include in includeTree)
{{
query = ApplyIncludeRecursively(query, include.Key, include.Value);
}}

return query;
}}

private static IQueryable<{item.ClassName}> ApplyIncludeRecursively(IQueryable<{item.ClassName}> query, string path, IncludeNode node)
{{
query = query.Include(path);

foreach (var child in node.Children)
{{
var childPath = string.Concat(path, ""."", child.Key);
query = ApplyIncludeRecursively(query, childPath, child.Value);
}}

return query;
}}
}}";

context.AddSource($"{item.ClassName}Controller.cs", generatedCode);
Expand Down
11 changes: 7 additions & 4 deletions Dappi.SourceGenerator/Generators/ActionsGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ public static string GenerateGetByIdAction(List<CrudActions> crudActions, Source

[HttpGet("{id}")]
{{PropagateDappiAuthorizationTags(item.AuthorizeAttributes, AuthorizeMethods.Get)}}
[IncludeQueryFilter]
public async Task<IActionResult> Get{{item.ClassName}}(Guid id, [FromQuery] string? fields = null)
{
try
{
if (id == Guid.Empty)
return BadRequest();

var query = dbContext.{{item.ClassName.Pluralize()}}.AsNoTracking().AsQueryable();
var query = dbContext.{{item.ClassName.Pluralize()}}.AsNoTracking().AsQueryable();

query = query{{includesCode}};
query = ApplyDynamicIncludes(query);

var result = await query
.FirstOrDefaultAsync(p => p.Id == id);
Expand Down Expand Up @@ -59,13 +60,14 @@ public static string GenerateGetAction(List<CrudActions> crudActions, SourceMode
[HttpGet]
{{PropagateDappiAuthorizationTags(item.AuthorizeAttributes, AuthorizeMethods.Get)}}
[CollectionFilter]
[IncludeQueryFilter]
public async Task<IActionResult> Get{{item.ClassName.Pluralize()}}([FromQuery] {{item.ClassName}}Filter? filter, [FromQuery] string? fields = null)
{
try
{
var query = dbContext.{{item.ClassName.Pluralize()}}.AsNoTracking().AsQueryable();

query = query{{includesCode}};
query = ApplyDynamicIncludes(query);

var filters = HttpContext.Items[CollectionFilter.FilterParamsKey] as List<Filter>;
if (filters is not null && filters.Count > 0)
Expand Down Expand Up @@ -115,11 +117,12 @@ public static string GenerateGetAllAction(List<CrudActions> crudActions, SourceM

[HttpGet("get-all")]
{{PropagateDappiAuthorizationTags(item.AuthorizeAttributes, AuthorizeMethods.Get)}}
[IncludeQueryFilter]
public async Task<IActionResult> GetAll{{item.ClassName.Pluralize()}}()
{
var query = dbContext.{{item.ClassName.Pluralize()}}.AsNoTracking();

query = query{{includesCode}};
query = ApplyDynamicIncludes(query);

return Ok(new {items = await query.ToListAsync()});
}
Expand Down