The IHierarchicalTopicMappingService<T> and its concrete implementation, HierarchicalTopicMappingService<T>, provide special handling for traversing hierarchical trees of view models.
While the TopicMappingService is capable of populating trees on its own, it is exclusively bound to honoring the rules defined by the attributes (such as [Include(associationTypes)] and [Flatten]). By contrast, the IHierarchicalTopicMappingService<T> offers three additional capabilities:
- The number of tiers in the hierarchy can be restricted to a set number (via the
tiersparameter onGetRootViewModelAsync()andGetViewModelAsync()), even if the[Include()]rules would allow full unlimited recursion. - The topics included can be constrained by specifying a validation method or lamda expression that accepts a
Topicas the parameter, and returns eithertrue(if theTopicshould be mapped) orfalse(if it should be skipped). - The type that all children will be mapped to can be specified, instead of letting the model type be determined exclusively by the
Topic.ContentTypeproperty.
In many cases, these are not needed. They do, however, provide additional flexibility for particular scenarios. For example, these are valuable for constructing the navigation used by e.g. the MenuViewComponentBase<T>, which should be restricted to three tiers, should be mapped to a NavigationTopicViewModel, and, in the case of the menu navigation, should exclude any topics of the content type PageGroup.
The CachedHierarchicalTopicMappingService<T> caches entries keyed based on the Topic.Id of the root Topic as well as the T argument of the type. Because of the mechanics of the HierarchicalTopicMappingService<T>, this cannot simply use the CachedTopicMappingService for caching, since each tier of navigation is mapped independently. This is necessary to apply the above business logic to the hierarchy, but makes it impossible for the CachedTopicMappingService to understand whether each request is suitable for caching.
Note: As with the
CachedTopicMappingService, theCachedHierarchicalTopicMappingservice should be used with caution. It will not be (immediately) updated if the underlying database or topic graph are updated. And since the topic graph is already cached, it effectively doubles the memory footprint of the graph by storing it both as topics as well as view models. That said, this is useful for large view model graphs that are frequently reused—such as those that show up in the navigation of a site.
The first code block demonstrates how to construct a new instance of a IHierarchicalTopicMappingService<T>. In this case, it wraps the default HierarchicalTopicMappingService<T> in a CachedHierarchicalTopicMappingService<T> for caching, and maps children to the NavigationTopicViewModel class from the Ignia.Topics.ViewModels project. Typically, this would be done in the Composition Root of an application, with the service passed into e.g. a MenuViewComponent as an IHierarchicalTopicMappingService<T> dependency.
var hierarchicalTopicMappingService = new CachedHierarchicalTopicMappingService<NavigationTopicViewModel>(
new HierarchicalTopicMappingService<NavigationTopicViewModel>(
_topicRepository,
_topicMappingService
);
);Once the IHierarchicalTopicMappingService<T> is initialized, the hierarchical mapping can be constructed by calling the main entry point, GetRootViewModelAsync(), which accepts three arguments:
Topic sourceTopic: The topic representing the root of the hierarchy. This could be the root topic in the database, but will more likely be the root of a subtree relative to the current request (e.g.,Root:Web).int tiers = 1: The number of tiers to crawl. While theTopicMappingServiceimplementation will crawl indefinitely, given the right conditions, theIHierarchicalTopicMappingService<T>can be constrained to a particular depth by the caller.Func<Topic, bool> validationDelegate = null: A validation function that accepts aTopicas input and returns eithertrueif theTopic(and its descendants) should be included, and otherwisefalse.
await hierarchicalTopicMappingService.GetRootViewModelAsync(
hierarchicalTopicMappingService.GetHierarchicalRoot(currentTopic, 2, "Web"),
2,
t => t.Parent?.ContentType != "PageGroup"
).ConfigureAwait(false),In this code example, the following arguments are used:
Topic sourceTopic: TheGetHierarchicalRoot()helper method is used to find a root that is at the second tier of the topic graph (right below the database root), but within the path of the current topic. So, for instance, if the current topic is atRoot:Customers:Support:Email, then theGetHierarchicalRoot()would returnRoot:Customers.int tiers = 1: The number of tiers is set to 2. So in the above example,Root:Customers:Support:Emailwould be included (sinceEmailis two tiers from the hierarchical root), but e.g.Root:Customers:Support:Email:Prioritywouldn't be.Func<Topic, bool> validationDelegate = null: The validation delegate will reject any topics under aPageGroup. Typically, pages of typePageGrouphave their own internal navigation, which shouldn't be duplicated in the primary navigation of the site.