Skip to content

Commit

Permalink
Fix JsonDynamicObject usage in Liquid (#17015)
Browse files Browse the repository at this point in the history
Co-authored-by: Sebastien Ros <[email protected]>
  • Loading branch information
MichaelPetrinolis and sebastienros authored Nov 14, 2024
1 parent e5f9b46 commit 6772f53
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 0 deletions.
9 changes: 9 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Liquid/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
using System.Globalization;
using System.Numerics;
using System.Text.Json;
using System;
using System.Text.Json.Dynamic;
using System.Text.Json.Nodes;
using Fluid;
Expand Down Expand Up @@ -49,6 +53,9 @@ public override void ConfigureServices(IServiceCollection services)
// When a property of a 'JsonObject' value is accessed, try to look into its properties.
options.MemberAccessStrategy.Register<JsonObject, object>((source, name) => source[name]);
// When a property of a 'JsonDynamicObject' value is accessed, try to look into its properties.
options.MemberAccessStrategy.Register<JsonDynamicObject, object>((json, name) => json[name]);
// Convert JToken to FluidValue
options.ValueConverters.Add(x =>
{
Expand All @@ -57,8 +64,10 @@ public override void ConfigureServices(IServiceCollection services)
JsonObject o => new ObjectValue(o),
JsonDynamicObject o => new ObjectValue((JsonObject)o),
JsonValue o => o.GetObjectValue(),
JsonDynamicValue o => ((JsonValue)(o.Node)).GetObjectValue(),
DateTime d => new ObjectValue(d),
_ => null
};
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using System.Text.Json.Dynamic;
using Fluid;
using Fluid.Values;
using Microsoft.AspNetCore.Html;
Expand Down
98 changes: 98 additions & 0 deletions test/OrchardCore.Tests/DisplayManagement/LiquidTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,103 @@ await context.UsingTenantScopeAsync(async scope =>
});
}

[Fact]
public async Task SortingContentItems_ShouldSortTheArrayOnIntegerField()
{
var context = new SiteContext();
await context.InitializeAsync();
await context.UsingTenantScopeAsync(async scope =>
{
var template = """
unsorted:{% for action in Model %}{{ action.Content.HotActionsPart.Order.Value }}{% endfor %}
{% assign hotActions = Model | sort: "Content.HotActionsPart.Order.Value" %}
sorted:{% for action in hotActions %}{{ action.Content.HotActionsPart.Order.Value }}{% endfor %}
""";
var json = JConvert.SerializeObject(FakeContentItems);
var testModel = JConvert.DeserializeObject<ContentItem[]>(json);
var liquidTemplateManager = scope.ServiceProvider.GetRequiredService<ILiquidTemplateManager>();
var result = await liquidTemplateManager.RenderStringAsync(template,
NullEncoder.Default,
testModel);
Assert.Equal("unsorted:302040605010sorted:102030405060", result.ReplaceLineEndings(""));
});
}

[Fact]
public async Task FilteringContentItems_ShouldFilterTheArrayOnBooleanField()
{
var context = new SiteContext();
await context.InitializeAsync();
await context.UsingTenantScopeAsync(async scope =>
{
var template = """
total:{{Model | size}}
{% assign hotActions = Model | where: "Content.HotActionsPart.AddtoHotActionsMenu.Value", true %}
filtered:{{ hotActions | size }}
""";
var json = JConvert.SerializeObject(FakeContentItems);
var testModel = JConvert.DeserializeObject<ContentItem[]>(json);
var liquidTemplateManager = scope.ServiceProvider.GetRequiredService<ILiquidTemplateManager>();
var result = await liquidTemplateManager.RenderStringAsync(template,
NullEncoder.Default,
testModel);
Assert.Equal("total:6filtered:5", result.ReplaceLineEndings(""));
});
}

[Fact]
public async Task FilteringAndSortingContentItems_ShouldFilterAndSortTheArrayOnContentFields()
{
var context = new SiteContext();
await context.InitializeAsync();
await context.UsingTenantScopeAsync(async scope =>
{
var template = """
original:{% for action in Model%}{{ action.Content.HotActionsPart.Order.Value }}{% endfor %}
{% assign hotActions = Model | where: "Content.HotActionsPart.AddtoHotActionsMenu.Value", true | sort: "Content.HotActionsPart.Order.Value" %}
filtered_sorted:{% for action in hotActions %}{{ action.Content.HotActionsPart.Order.Value }}{% endfor %}
""";
var json = JConvert.SerializeObject(FakeContentItems);
var testModel = JConvert.DeserializeObject<ContentItem[]>(json);
var liquidTemplateManager = scope.ServiceProvider.GetRequiredService<ILiquidTemplateManager>();
var result = await liquidTemplateManager.RenderStringAsync(template,
NullEncoder.Default,
testModel);
Assert.Equal("original:302040605010filtered_sorted:1030405060", result.ReplaceLineEndings(""));
});
}


public static ContentItem[] FakeContentItems => new[] { "30_true", "20_false", "40_true", "60_true", "50_true", "10_true" }.Select(x => CreateFakeContentItem(decimal.Parse(x.Split('_')[0]), x.Split('_')[1] == "true")).ToArray();

public static ContentItem CreateFakeContentItem(decimal order, bool addtoHotActionsMenu)
{
var contentItem = new ContentItem();
contentItem.GetOrCreate<ContentPart>("HotActionsPart");
contentItem.Alter<ContentPart>("HotActionsPart", x =>
{
x.GetOrCreate<BooleanField>("AddtoHotActionsMenu");
x.Alter<BooleanField>("AddtoHotActionsMenu", f => f.Value = addtoHotActionsMenu);
x.GetOrCreate<NumericField>("Order");
x.Alter<NumericField>("Order", f => f.Value = order);
});
return contentItem;
}

public class MyPart : ContentPart
{
public string Text { get; set; }
Expand All @@ -119,4 +216,5 @@ public class MyField : ContentField
{
public int Value { get; set; }
}

}

0 comments on commit 6772f53

Please sign in to comment.