Is your feature request related to a problem? Please describe.
If you want to load Linked Items of the particular item, it is required to transform them into IDocument
to be able to pass them through the pipeline and i.e. use them to create a view model for the Razor view. It would be great to have this feature out of the box.
I will describe the situation in the example:
You have a "Root" item that is the root of your website, this contains the Linked Items element (Subpages
) model menu structure. Every item (Page
type) then contains another Subpages
element to allow having a multilevel navigation menu. Plus the Page
model contains "Content" Linked items element containing typically one item holding the channel-agnostic content.
This is basically a Webspotlight setup.
Example model structure:
public partial class Root
{
public IEnumerable<object> Subpages { get; set; } // Strongly types items of type i.e. Page
}
public partial class Page
{
public IEnumerable<object> Subpages { get; set; } // Strongly types items of type i.e. Page
public IEnumerable<object> Content { get; set; } // Contains only one "Content" item e.g. LandingPage
}
public partial class LandingPage
{
public string Headline { get; set; }
public IRichTextContent MainText { get; set; }
public IRichTextContent Summary { get; set; }
}
Now if you want to load the data from Root
and render data from the linked items in razor views, you would do something like:
InputModules = new ModuleList {
new Kontent<Root>(client)
.WithQuery(
new EqualsFilter("system.codename", "root"),
new LimitParameter(1),
new DepthParameter(3)
),
new ReplaceDocuments(
new ExecuteConfig(
Config.FromDocument((doc, context) =>
// *********** THIS PART COULD BE PART OF THE Kontent.Statiq MODULE ************
doc.AsKontent<Root>().Subpages.ToList().Select(subpage =>
{
var pageContent = (subpage as Page)?.Content.FirstOrDefault();
if(pageContent == null)
{
throw new InvalidDataException("Root page (codename: root, type: root) does not contain any pages, or any page does not contain exactly content block!");
}
return context.CreateDocument(
CreateKontentDocument(context, pageContent));
})
)
)
)
};
// ...
ProcessModules = new ModuleList {
new MergeContent(new ReadFiles("LandingPage.cshtml")),
new RenderRazor()
.WithModel(Config.FromDocument((document, context) =>
{
var typeCodename = document
.FilterMetadata(KontentKeys.System.Type)
.Values
?.FirstOrDefault()
?.ToString();
Type type = typeProvider.GetType(typeCodename);
var landingPageType = typeof(LandingPage);
if(landingPageType == type)
{
return document.AsKontent<LandingPage>();
}
else
{
throw new InvalidDataException("Unsuported content type of the Page's Content element");
}
})),
// ****
};
// basically copy&paste of the https://github.com/alanta/Kontent.Statiq/blob/main/Kontent.Statiq/Kontent.cs#L49
private IDocument CreateKontentDocument(IExecutionContext context, object item)
{
var props = item.GetType().GetProperties(BindingFlags.Instance | BindingFlags.FlattenHierarchy |
BindingFlags.GetProperty | BindingFlags.Public);
var metadata = new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(TypedContentExtensions.KontentItemKey, item),
};
if (props.FirstOrDefault(prop => typeof(IContentItemSystemAttributes).IsAssignableFrom(prop.PropertyType))
?.GetValue(item) is IContentItemSystemAttributes systemProp)
{
metadata.AddRange(new[]
{
new KeyValuePair<string, object>(KontentKeys.System.Name, systemProp.Name),
new KeyValuePair<string, object>(KontentKeys.System.CodeName, systemProp.Codename),
new KeyValuePair<string, object>(KontentKeys.System.Language, systemProp.Language),
new KeyValuePair<string, object>(KontentKeys.System.Id, systemProp.Id),
new KeyValuePair<string, object>(KontentKeys.System.Type, systemProp.Type),
new KeyValuePair<string, object>(KontentKeys.System.LastModified, systemProp.LastModified)
});
}
return context.CreateDocument(metadata, null, "text/html");
}
In the CreateKontentDocument
I have only removed the part of GetContent
delegate for simplicity and using item.GetType().GetProperties
instead of typeof<TContentModel>.GetProperties
Describe the solution you'd like
It would be great to have the functionality to transfer Linked Items to IEnumerable<IDocument>
right in the SDK.
doc.LinkedItemsAsKontentDocuments<Root>(root => root.Subpages);
Describe alternatives you've considered
I was thinking about a separate module, but I think it is overengineering.
Additional context
I have the solution in the https://github.com/Kentico/jamstackon.net/blob/linked-items-idocument-transformation/Pipelines/RootPipeline.cs
- I have invited you to the project @alanta