Skip to content

Commit e0a4b12

Browse files
Custom Content Resolver Support (#14)
<!-- Provide a general summary of your changes in the Title above --> <!-- Apply the label "bug" or "enhacement" as applicable. --> ## Description / Motivation <!-- Describe your changes in detail --> <!-- Why is this change required? What problem does it solve? --> <!-- If it fixes an open issue, please link to the issue here. --> This modifies the FieldParser to support Custom Content Resolvers returning modified JSON. This fixes #8 Added missing CONTRIBUTING file. Removed `AddSystemTextJson` which should not be used or needed as it pollutes the DI. ## Testing Added integration tests both for layout client and rendering engine binding leveraging the "Navigation" component from Headless SXA. - [X] The Unit & Intergration tests are passing. - [X] I have added the necesary tests to cover my changes. ## Terms <!-- Place an X in the [] to check. --> <!-- The Code of Conduct helps create a safe space for everyone. We require that everyone agrees to it. --> - [X] I agree to follow this project's [Code of Conduct](CODE_OF_CONDUCT.md). --------- Co-authored-by: Ivan Lieckens <ivanlieckens@hotmail.com>
1 parent a79266c commit e0a4b12

18 files changed

Lines changed: 648 additions & 35 deletions

File tree

CONTRIBUTING.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Contribution to the Sitecore ASP.NET Core SDK
2+
3+
Thank you for your interest in contributing to our project. You can contribute with issues and PRs. Simply filing issues for problems you encounter is a great way to contribute. Contributing implementations is greatly appreciated.
4+
5+
## Code of Conduct
6+
Please read the [Code of Conduct](./CODE_OF_CONDUCT.md) before participating, all contributors are expected to uphold this code. Please report unacceptable behavior to [community@sitecore.com](mailto:community@sitecore.com).
7+
8+
## Reporting Issues
9+
10+
We always welcome bug reports, feature requests and overall feedback. Here are a few tips on how you can make reporting your issue as effective as possible.
11+
12+
### Finding Existing Issues
13+
14+
Before filing a new issue, please search our [open issues](https://github.com/Sitecore/ASP.NET-Core-SDK/issues) to check if it already exists.
15+
16+
If you do find an existing issue, please include your own feedback in the discussion. Do consider upvoting (reaction) the original post, as this helps us prioritize popular issues.
17+
18+
### Use the right template
19+
20+
When creating a new issue, please use the appropriate template. We have the following templates available:
21+
22+
| Template | Description |
23+
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
24+
| Bug Report | Use this template to report a bug. |
25+
| Feature Request | Use this template to request a new feature. |
26+
| Question | Use this template to ask a question. For implementation specific questions please use [StackExchange](https://sitecore.stackexchange.com/). |
27+
| Report a security vulnerability | Use this template to report a security vulnerability. |
28+
29+
### Writing a Good Bug Report
30+
31+
Good bug reports make it easier for maintainers to verify and root cause the underlying problem. The better a bug report, the faster the problem will be resolved. Ideally, a bug report should contain the following information:
32+
33+
* A high-level description of the problem.
34+
* A _minimal reproduction_, i.e. the smallest size of code/configuration required to reproduce the wrong behavior.
35+
* A description of the _expected behavior_, contrasted with the _actual behavior_ observed.
36+
* Information on the environment: Sitecore XM version, SDK version, etc.
37+
* Additional information, e.g. is it a regression from previous versions? are there any known workarounds?
38+
39+
#### Why are Minimal Reproductions Important?
40+
41+
A reproduction lets maintainers verify the presence of a bug, and diagnose the issue using a debugger. A _minimal_ reproduction is the smallest possible application demonstrating that bug. Minimal reproductions are generally preferable since they:
42+
43+
1. Focus debugging efforts on a simple code snippet,
44+
2. Ensure that the problem is not caused by unrelated dependencies/configuration,
45+
3. Avoid the need to share production codebases.
46+
47+
#### Are Minimal Reproductions Required?
48+
49+
In certain cases, creating a minimal reproduction might not be practical (e.g. due to nondeterministic factors, external dependencies). In such cases you would be asked to provide as much information as possible, for example by sharing a memory dump of the failing application. If maintainers are unable to root cause the problem, they might still close the issue as not actionable. While not required, minimal reproductions are strongly encouraged and will significantly improve the chances of your issue being prioritized and fixed by the maintainers.
50+
51+
#### How to Create a Minimal Reproduction
52+
53+
The best way to create a minimal reproduction is gradually removing code and dependencies from a reproducing app, until the problem no longer occurs. A good minimal reproduction:
54+
55+
* Excludes all unnecessary types, methods, code blocks, source files, nuget dependencies and project configurations.
56+
* Contains documentation or code comments illustrating expected vs actual behavior.
57+
* If possible, avoids performing any unneeded IO or system calls.
58+
59+
## Contributiting Code
60+
All code contributions must be submitted by the standard GitHub pull request flow, or by logging an issue.
61+
62+
### Accepting Contributions
63+
Sitecore has a _"No commitment"_ approach to this repository. The required functionality of the SDK and related XM Cloud features will be the driver for acceptance decisions. We are open to contributions from the community, but make no commitment to accept or incorporate changes.
64+
65+
### Guidelines
66+
* Create a [fork of the repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo)
67+
* Create a branch for your changes. Use /feat/ for new features, /fix/ for bug fixes and /new/ for breaking changes.
68+
* We observe strict code style rules, please ensure your changes follow the guidance provided by the analyzers.
69+
* [StyleCop](https://github.com/DotNetAnalyzers/StyleCopAnalyzers)
70+
* [File Scoped Namespaces](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/file-scoped-namespaces)
71+
* Do not use `var`
72+
* We require all changes to be covered by tests.
73+
* When you are ready to submit your changes, [create a pull request from your fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) into this repository.
74+
* This repository is configured to use `Squash Merges` for all accepted contributions. This decision has been made to keep the commit history clean and concise.

Sitecore.AspNetCore.SDK.sln

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sitecore.AspNetCore.SDK.Tra
8989
EndProject
9090
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{5FE82369-DEF2-4136-B74F-6E86DB91050E}"
9191
ProjectSection(SolutionItems) = preProject
92+
CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md
93+
CONTRIBUTING.md = CONTRIBUTING.md
94+
LICENSE.md = LICENSE.md
9295
.github\pull_request_template.md = .github\pull_request_template.md
96+
README.md = README.md
9397
EndProjectSection
9498
EndProject
9599
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{1706E43D-AC19-4FBB-9BFB-18A8B195580A}"

src/Sitecore.AspNetCore.SDK.LayoutService.Client/Extensions/SitecoreLayoutClientBuilderExtensions.cs

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public static ILayoutRequestHandlerBuilder<GraphQlLayoutServiceHandler> AddGraph
9999
request.Language(defaultLanguage);
100100
}
101101
});
102-
return builder.AddHandler(name, (sp)
102+
return builder.AddHandler(name, sp
103103
=> ActivatorUtilities.CreateInstance<GraphQlLayoutServiceHandler>(
104104
sp, client, sp.GetRequiredService<ISitecoreLayoutSerializer>(), sp.GetRequiredService<ILogger<GraphQlLayoutServiceHandler>>()));
105105
}
@@ -151,23 +151,6 @@ public static ISitecoreLayoutClientBuilder WithDefaultRequestOptions(this ISitec
151151
return builder;
152152
}
153153

154-
/// <summary>
155-
/// Configures System.Text.Json specific features such as input and output formatters.
156-
/// </summary>
157-
/// <param name="builder">The <see cref="ISitecoreLayoutClientBuilder"/> being configured.</param>
158-
/// <returns>The <see cref="ILayoutRequestHandlerBuilder{THandler}"/> so that additional calls can be chained.</returns>
159-
public static ISitecoreLayoutClientBuilder AddSystemTextJson(this ISitecoreLayoutClientBuilder builder)
160-
{
161-
ServiceDescriptor descriptor = new(typeof(ISitecoreLayoutSerializer), typeof(JsonLayoutServiceSerializer), ServiceLifetime.Singleton);
162-
builder.Services.Replace(descriptor);
163-
164-
builder.Services.AddSingleton<IFieldParser, FieldParser>();
165-
builder.Services.AddSingleton<JsonConverter, FieldConverter>();
166-
builder.Services.AddSingleton<JsonConverter, PlaceholderFeatureConverter>();
167-
168-
return builder;
169-
}
170-
171154
/// <summary>
172155
/// Registers a HTTP request handler for the Sitecore layout service client.
173156
/// </summary>

src/Sitecore.AspNetCore.SDK.LayoutService.Client/Serialization/Converter/FieldParser.cs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,44 @@ namespace Sitecore.AspNetCore.SDK.LayoutService.Client.Serialization.Converter;
77
/// <inheritdoc cref="IFieldParser"/>
88
public class FieldParser : IFieldParser
99
{
10+
/// <summary>
11+
/// Field key for custom content created by Custom Content Resolvers.
12+
/// </summary>
13+
// ReSharper disable once MemberCanBePrivate.Global - Must be accessible for people using the SDK.
14+
public const string CustomContentFieldKey = "CustomContent";
15+
1016
/// <inheritdoc cref="IFieldParser.ParseFields"/>
1117
public Dictionary<string, IFieldReader> ParseFields(ref Utf8JsonReader reader)
1218
{
13-
if (reader.TokenType != JsonTokenType.StartObject)
19+
Dictionary<string, IFieldReader> result = [];
20+
switch (reader.TokenType)
1421
{
15-
throw new JsonException();
22+
case JsonTokenType.StartObject:
23+
result = ParseStandardFields(ref reader);
24+
break;
25+
default:
26+
result.Add(CustomContentFieldKey, new JsonSerializedField(ParseField(ref reader)));
27+
break;
1628
}
1729

18-
Dictionary<string, IFieldReader> fields = [];
30+
return result;
31+
}
32+
33+
private static Dictionary<string, IFieldReader> ParseStandardFields(ref Utf8JsonReader reader)
34+
{
35+
Dictionary<string, IFieldReader> result = [];
1936
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
2037
{
2138
string? key = reader.GetString();
2239
reader.Read();
2340
JsonDocument value = ParseField(ref reader);
2441
if (key != null)
2542
{
26-
fields.Add(key, new JsonSerializedField(value));
43+
result.Add(key, new JsonSerializedField(value));
2744
}
2845
}
2946

30-
return fields;
47+
return result;
3148
}
3249

3350
private static JsonDocument ParseField(ref Utf8JsonReader reader)

tests/Sitecore.AspNetCore.SDK.LayoutService.Client.Integration.Tests/FieldsFixture.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,4 +493,30 @@ public void Component_RichTextField_CanBeRead(ISitecoreLayoutSerializer serializ
493493
resultField!.Value.Should().Be(expectedField.value.Value);
494494
resultField.EditableMarkup.Should().Be(expectedField.editable.Value);
495495
}
496+
497+
[Theory]
498+
[MemberData(nameof(Serializers))]
499+
public void HeadlessSxa_CanBeRead(ISitecoreLayoutSerializer serializer)
500+
{
501+
// Arrange
502+
string json = File.ReadAllText("./Json/headlessSxa.json");
503+
dynamic jsonModel = JObject.Parse(json);
504+
505+
// Act
506+
SitecoreLayoutResponseContent? result = serializer.Deserialize(json);
507+
508+
// Assert
509+
TextField? resultField = result?.Sitecore?.Route?
510+
.Placeholders["headless-header"].ComponentAt(0)?
511+
.Placeholders["sxa-header"].ComponentAt(0)?
512+
.Fields["Text"]
513+
.Read<TextField>();
514+
515+
dynamic? expectedField = jsonModel.sitecore.route
516+
.placeholders["headless-header"][0]
517+
.placeholders["sxa-header"][0]
518+
.fields.Text;
519+
520+
resultField!.Value.Should().Be(expectedField.value.Value);
521+
}
496522
}

tests/Sitecore.AspNetCore.SDK.LayoutService.Client.Integration.Tests/Sitecore.AspNetCore.SDK.LayoutService.Client.Integration.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
<Link>Json\edit-in-horizon-mode.json</Link>
1818
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
1919
</Content>
20+
<Content Include="..\data\json\headlessSxa.json">
21+
<Link>Json\headlessSxa.json</Link>
22+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
23+
</Content>
2024
</ItemGroup>
2125

2226
</Project>

tests/Sitecore.AspNetCore.SDK.LayoutService.Client.Tests/Serialization/Converter/FieldParserTests.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,22 @@ public class FieldParserTests
2727
};
2828

2929
[Fact]
30-
public void ParseFields_IncorrectJsonNotObject_ShouldThrowJsonException()
30+
public void ParseFields_JsonNotObject_ShouldWrapAsCustomContent()
3131
{
3232
// Arrange
33-
void Read()
34-
{
35-
const string json = "[]";
36-
byte[] bytes = [.. Encoding.UTF8.GetBytes(json)];
37-
Utf8JsonReader reader = new(bytes);
38-
reader.Read();
39-
_sut.ParseFields(ref reader);
40-
}
33+
const string json = "[]";
34+
byte[] bytes = [.. Encoding.UTF8.GetBytes(json)];
35+
Utf8JsonReader reader = new(bytes);
36+
reader.Read();
4137

4238
// Act
43-
Action result = Read;
39+
Dictionary<string, IFieldReader> result = _sut.ParseFields(ref reader);
4440

4541
// Assert
46-
result.Should().Throw<JsonException>();
42+
result.Should().ContainSingle();
43+
(string key, IFieldReader value) = result.First();
44+
key.Should().Be(FieldParser.CustomContentFieldKey);
45+
value.Should().BeOfType<JsonSerializedField>();
4746
}
4847

4948
[Fact]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Sitecore.AspNetCore.SDK.LayoutService.Client.Serialization.Converter;
2+
using Sitecore.AspNetCore.SDK.RenderingEngine.Binding.Attributes;
3+
4+
namespace Sitecore.AspNetCore.SDK.RenderingEngine.Integration.Tests.ComponentModels;
5+
6+
public class CustomResolver
7+
{
8+
[SitecoreComponentField(Name = FieldParser.CustomContentFieldKey)]
9+
public CustomResolverModel[]? CustomContent { get; set; }
10+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Sitecore.AspNetCore.SDK.LayoutService.Client.Response.Model.Fields;
2+
3+
namespace Sitecore.AspNetCore.SDK.RenderingEngine.Integration.Tests.ComponentModels
4+
{
5+
public class CustomResolverModel
6+
{
7+
public List<string>? Styles { get; set; } = [];
8+
9+
public List<CustomResolverModel>? Children { get; set; } = [];
10+
11+
public string? Href { get; set; }
12+
13+
public string? Querystring { get; set; }
14+
15+
public TextField? NavigationTitle { get; set; }
16+
}
17+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Sitecore.AspNetCore.SDK.RenderingEngine.Binding.Attributes;
2+
3+
namespace Sitecore.AspNetCore.SDK.RenderingEngine.Integration.Tests.ComponentModels;
4+
5+
public class PartialDesignDynamicPlaceholder
6+
{
7+
[SitecoreComponentParameter(Name ="sig")]
8+
public string? Sig { get; set; }
9+
}

0 commit comments

Comments
 (0)