Calling all developers! We invite you to provide your input on Feature Experimentation by completing this brief survey.
Calling all developers! We invite you to provide your input on Feature Experimentation by completing this brief survey.
a) Though the nodes tree is displayed correctly, the "Belongs to / Primary category" tab is empty - it doesn't display anything.
=> if you have a custom catalog content provider, you'd very likely need to implement your own IRelationRepository
Thank you very much Quan for your response.
Unfortunately, the mistery is much bigger than this, because:
a) I implemened a custom IRelationRepository and register it correctly.
b) the GetChildren<T> and GetParents<T> method gets called without any issues if the node is of standard provider, but for my custom provider it doesn't get called for any node.
c) the GetParents<T> method gets called for my custom provider only for Variants and it seems that the call is being used to get the main product to which the variant is being asigned (the information that is displayed in the main part of the variant details - the gray area) - but though the information about the product is being displayed in the top part, in the "Belongs to / Variants / Relations" tab of the variant, nothing is displayed.
I'm thinking that I should implement something extra to make it call my custom IRelationRepository methods, but I have no idea what. Please Quan do you have any idea what am I missing?
BTW - thank you for all your community efforts and also for your books - to be honest it's a real honour that you replied to my question.
Thank you again!
Evdin
Thank you for your kind words. I try to help whenever I can :)
If it is not much trouble could you show us your implementation of IRelationRepository and how you register it? I'm pretty sure the relations in the screenshot you posted were from RelationStore which uses IRelationRepository internally. If you debug and open the view, do you see your implementation of IRelationRepository.GetChildren and GetParents hit?
Hello and thank you for your time.
First, the IRelationRepository implementation.
public class MagicBoxCatalogRelationRepository : IRelationRepository
{
#region Private properties
private readonly IRelationRepository defaultImplementation;
private readonly MagicBoxCatalogIdentityService catalogIdentityService;
private readonly IdentityMappingService identityMappingService;
private readonly CacheOrApiCatalogOperations cacheOrApiCatalogOperations;
private readonly ContentLoader contentLoader;
#endregion
#region Constructor
/// <summary>
/// Constructor.
/// </summary>
/// <param name="defaultImplementation"></param>
/// <param name="identityService"></param>
/// <param name="identityMappingService"></param>
/// <param name="cacheOrApiCatalogOperations"></param>
/// <param name="contentLoader"></param>
public MagicBoxCatalogRelationRepository(
IRelationRepository defaultImplementation,
MagicBoxCatalogIdentityService identityService,
IdentityMappingService identityMappingService,
CacheOrApiCatalogOperations cacheOrApiCatalogOperations,
ContentLoader contentLoader)
{
this.defaultImplementation = defaultImplementation;
this.catalogIdentityService = identityService;
this.identityMappingService = identityMappingService;
this.cacheOrApiCatalogOperations = cacheOrApiCatalogOperations;
this.contentLoader = contentLoader;
}
#endregion
#region Get children
/// <summary>
/// Get list of children entries for the given parent link.
/// </summary>
/// <param name="parentLink"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public IEnumerable<T> GetChildren<T>(ContentReference parentLink) where T : Relation
{
#region Validate the parameters
if (parentLink.IsStandardContentProvider())
return this.defaultImplementation.GetChildren<T>(parentLink);
if (!parentLink.IsMagicBoxContentProvider() || ContentReference.IsNullOrEmpty(parentLink))
return Enumerable.Empty<T>();
#endregion
#region Initialize the main properties
MappedIdentity identity = this.identityMappingService.Get(parentLink);
NameValueCollection metaData = this.catalogIdentityService.ParseExternalIdentifier(identity.ExternalIdentifier);
string entryType = metaData.GetEntryType();
List<T> childRelations = new List<T>();
#endregion
#region If the current entry is not a product then we don't care
if (entryType == NodesConstants.Product)
{
string productNodeId = metaData.GetCatalogProductCode();
string categoryNodeId = metaData.GetCatalogCategoryCode();
Guid catalogIdentifier = metaData.GetCatalogIdentifier();
List<ItemElementApiResponseModel>? childrenItems =
this.cacheOrApiCatalogOperations.GetCatalogChildrenItems(productNodeId);
if (childrenItems == null)
return childRelations;
List<ContentReference> variantReferences = this.catalogIdentityService
.CreateVariantContentReferences(catalogIdentifier.ToString(), categoryNodeId, childrenItems,
true)
.ToList();
foreach (ContentReference variantReference in variantReferences)
{
ProductVariation productVariation = new ProductVariation()
{
Parent = parentLink,
Child = variantReference,
GroupName = null,
Quantity = EntryRelation.DefaultQuantity,
SortOrder = 1,
};
T? productVariantRelation = productVariation as T;
ArgumentNullException.ThrowIfNull(productVariantRelation);
childRelations.Add(productVariantRelation);
}
}
return childRelations;
#endregion
}
#endregion
#region Get parents
/// <summary>
/// Get the parents of the current element.
/// </summary>
/// <param name="childLink"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public IEnumerable<T> GetParents<T>(ContentReference childLink) where T : Relation
{
#region Validate the parameters
if (childLink.IsStandardContentProvider())
return this.defaultImplementation.GetParents<T>(childLink);
if (!childLink.IsMagicBoxContentProvider() || ContentReference.IsNullOrEmpty(childLink))
return Enumerable.Empty<T>();
#endregion
#region Initialize the main properties
List<T> parentRelations = new List<T>();
MappedIdentity identity = this.identityMappingService.Get(childLink);
NameValueCollection metaData = this.catalogIdentityService.ParseExternalIdentifier(identity.ExternalIdentifier);
string entryType = metaData.GetEntryType();
Guid catalogId = metaData.GetCatalogIdentifier();
string categoryNodeId = metaData.GetCatalogCategoryCode();
CatalogContent catalog = this.contentLoader.Get<CatalogContent>(catalogId);
#endregion
switch (entryType)
{
case NodesConstants.Category:
break;
case NodesConstants.Product:
#region Get product parent relation
ContentReference parentCategoryContentReference =
this.catalogIdentityService.CreateCategoryContentReference(categoryNodeId, catalogId.ToString(),
true);
if (typeof(T) == typeof(NodeRelation))
{
NodeRelation nodeRelation = new NodeRelation
{
Parent = parentCategoryContentReference,
Child = childLink,
SortOrder = 1,
TargetCatalog = catalog.ContentLink
};
T? relation = nodeRelation as T;
ArgumentNullException.ThrowIfNull(relation);
parentRelations.Add(relation);
}
if (typeof(T) == typeof(NodeEntryRelation))
{
NodeEntryRelation nodeProductRelation = new NodeEntryRelation
{
Parent = parentCategoryContentReference,
Child = childLink,
TargetCatalog = catalog.ContentLink,
SortOrder = 1,
IsPrimary = true
};
T? relation = nodeProductRelation as T;
ArgumentNullException.ThrowIfNull(relation);
parentRelations.Add(relation);
}
#endregion
break;
case NodesConstants.Variant:
#region Get variant parent relation
if (typeof(T) == typeof(NodeRelation))
{
ContentReference categoryContentReference =
this.catalogIdentityService.CreateCategoryContentReference(categoryNodeId, catalogId.ToString(),
true);
NodeEntryRelation nodeVariantRelation = new NodeEntryRelation
{
Parent = categoryContentReference,
Child = childLink,
TargetCatalog = catalog.ContentLink,
SortOrder = 1,
IsPrimary = false
};
T? relation = nodeVariantRelation as T;
ArgumentNullException.ThrowIfNull(relation);
parentRelations.Add(relation);
}
if (typeof(T) == typeof(ProductVariation))
{
string productNodeId = metaData.GetCatalogProductCode();
ContentReference parentProductReference =
this.catalogIdentityService.CreateProductContentReference(productNodeId, categoryNodeId,
catalogId.ToString(), true);
ProductVariation nodeVariantRelation = new ProductVariation
{
Parent = parentProductReference,
Child = childLink,
Quantity = EntryRelation.DefaultQuantity,
SortOrder = 1,
};
T? relation = nodeVariantRelation as T;
ArgumentNullException.ThrowIfNull(relation);
parentRelations.Add(relation);
}
#endregion
break;
}
return parentRelations;
}
#endregion
#region Unused methods
public void RemoveRelations(IEnumerable<Relation> relations)
{
throw new NotImplementedException();
}
public void UpdateRelations(IEnumerable<Relation> relations)
{
throw new NotImplementedException();
}
public void SetNodeParent(ContentReference contentLink, ContentReference newParentLink)
{
throw new NotImplementedException();
}
#endregion
}
Then, the registration is being made through a InitializationModule:
/// <summary>
/// Configure container.
/// </summary>
/// <param name="context"></param>
public void ConfigureContainer(ServiceConfigurationContext context)
{
context.Services.AddSingleton<MagicBoxCatalogIdentityService>();
context.Services.AddSingleton<MagicBoxCatalogContentProvider>();
context.Services.AddSingleton<CatalogCategoryContentFactory>();
context.Services.AddSingleton<CatalogProductContentFactory>();
context.Services.AddSingleton<CatalogArticleContentFactory>();
ServiceDescriptor catalogContentServiceProviderDescriptor =
ServiceDescriptor.Singleton<CatalogContentProvider, MagicBoxCatalogIntermediaryContentProvider>();
context.Services.Replace(catalogContentServiceProviderDescriptor);
ServiceDescriptor catalogReferenceConverterProviderDescriptor =
ServiceDescriptor.Singleton<ReferenceConverter, MagicBoxCatalogReferenceConverter>();
context.Services.Replace(catalogReferenceConverterProviderDescriptor);
ServiceDescriptor catalogContentStructureProviderDescriptor = ServiceDescriptor
.Singleton<CatalogContentStructureProvider, MagicBoxCatalogContentStructureProvider>();
context.Services.Replace(catalogContentStructureProviderDescriptor);
context.Services.Intercept<IRelationRepository>((provider, defaultImplementation) =>
new MagicBoxCatalogRelationRepository(defaultImplementation,
provider.GetInstance<MagicBoxCatalogIdentityService>(),
provider.GetInstance<IdentityMappingService>(),
provider.GetInstance<CacheOrApiCatalogOperations>(),
provider.GetInstance<ContentLoader>()));
}
If I'm debugging, what is happening is pretty crazy:
a) in the admin, if I go to view a standard node (created manually in the admin) both methods gets hit.
b) in the admin, if I go to view an external node, my implementation gets called on the GetChildren for products and GetParents for variants, but not for nodes.
Thank you again.
Evdin
Things look correct from my side. You're very welcome to contact developer support service and refer me. If the problem is reproduced it could be a quick thing to debug and find out what's wrong
Hello again.
I fixed it!!! Long story short - I looked at the stack trace and there was nothing out of ordinary in the methods that called GetParents, so I thought that there should be a difference between the Node that the standard provider retrieves from the database and populates with attributes, and my node that it's coming from the special custom provider - it seems that this was the issue, and to be more precise:
1) DON'T EVER DO "NEW" on the following properties:
productContent.Categories = new...
productContent.ParentEntries = new...
productContent.Associations = new...
2) WHEN YOU INITIALIZE YOUR MODEL, DON'T DO "NEW" - instead, use
T entryContentBase = this.contentRepository.GetDefault<T>(parentContentReference);
3) JUST SET THE CURRENT ENTITY CONTENT LINK ON THOSE PROPERTIES:
productContent.Categories.ContentLink = productMappedIdentity.ContentLink;
productContent.ParentEntries.ContentLink = productMappedIdentity.ContentLink;
productContent.Associations.ContentLink = productMappedIdentity.ContentLink;
The previous example was for products and variants; for nodes, just do
nodeContent.Categories.ContentLink = categoryMappedIdentity.ContentLink;
And - that's it - the IRelationRepository methods are being called and everything works like a charm :-).
Thank you again for your responses and for your confirmation.
Evdin
Hello,
After two days of strugle with this issue, I'm (partially) giving up. Long story short - I have a custom CatalogContentProvider that works very well in the Admin UI - I can see the category (nodes) tree as expected and everything works very well.
There are two big problems and I really hope that someone can lead me in the correct direction:
a) Though the nodes tree is displayed correctly, the "Belongs to / Primary category" tab is empty - it doesn't display anything.
b) I don't know if it's related or not, if I'm trying to access a node (category) in the browser, I always receive 404 for all categories retrieved through the CatalogContentProvider; for all the others, everything is working well and I can see the categories in the browser.
First, regarding the versions of the packages that I'm using:
<PackageReference Include="EPiServer.Commerce" Version="14.33.0" />
Second, regarding the creation of the category through the custom catalog content provider, this is what I'm setting:
So, as you can see - the parent link is set.
And this is how the admin UI looks like:
Is there someone who can, please, have an idea about why this is empty or why the category is not being displayed in the browser and throw 404?
BTW, if I'm trying to open a category URL that comes through the custom CatalogContentProvider, this is what I see in the error log:
Request reached the end of the middleware pipeline without being handled by application code.
Thank you very much!