MvcSiteMapProvider ISiteMapBuilder combined with IDynamicNodeProvider

I am using MvcSiteMapProvider 4.4.3 to dynamically build a Sitemap from a database. I am following this article: https://github.com/maartenba/MvcSiteMapProvider/wiki/Multiple-Sitemaps-in-One-Application because I am using multiple Sitemaps.

This works and this is the main structure that is returned:

  • home
    • news
    • Products
    • ABOUT
    • Contact

One of the nodes ( /Products

) should be dynamically populated again based on different data. Do I need a node implementation IDynamicNodeProvider

for this /Products

? (please correct me if i'm wrong?)

Anyway, I think I need the above. The documentation shows ways to do this on a node defined in XML and on a node defined using controller action attributes, but not "manually" in ISiteMapBuilder

. So if I set an .DynamicNodeProvider

instance property ISiteMapNode

it doesn't seem to get an instance ... The property .HasDynamicNodeProvider

returns as well false

.

Looking at the source, I see - PluginProvider

stuff related to DynamicNodeProviderStrategy

, and there you go, they lost me ...

How to create ISiteMapNode

the " /Products

" my ISiteMapBuilder

, to his descendants ( /Products/Cat

and /Products/Cat/Product

) is dynamically loaded from the database?

0


source to share


1 answer


You can do this with ISiteMapBuilder, but you are probably better off using ISiteMapNodeProvider. The reason is that adding nodes to the SiteMap must be done at the very end of the process after all nodes have been created to ensure that each node is correctly mapped to the parent node (except, of course, the root node which does not need a parent). This was a major design change that was made in 4.3.0.

The default SiteMapBuilder class is already configured to provide

  • Nodes map correctly to parent nodes
  • There is only 1 root node
  • All nodes are added to SiteMap
  • Visitors are executed last after the SiteMap is fully assembled.

It is not possible to add multiple instances of ISiteMapBuilder as it circumvents this important logic. Therefore, it is best if you do not implement ISiteMapBuilder, but instead implement ISiteMapNodeProvider.

The SiteMapBuilder class uses the ISiteMapNodeProvider as a dependency through its constructor. You can use the CompositeSiteMapNodeProvider class to handle multiplicity in this interface so that more than one ISiteMapNodeProvider implementation can be added as needed.

The ISiteMapNodeProvider interface looks like this:

public interface ISiteMapNodeProvider
{
    IEnumerable<ISiteMapNodeToParentRelation> GetSiteMapNodes(ISiteMapNodeHelper helper);
}

      



There is only one method to implement. In addition, many of the common (but optional) services are injected through the interface automatically from the SiteMapBuilder class through ISiteMapNodeHelper.

This class is at a lower level than IDynamicNodeProvider. You are interacting directly with the ISiteMapNode, but all interaction with the SiteMap class is handled by the SiteMapBuilder. The ISiteMapNode is wrapped in an ISiteMapNodeToParentRelation instance that sits there to ensure that its parent node key can be traced back to the time it is added to the SiteMap object.

Your SiteMapNodeProvider should look something like this:

public class CustomSiteMapNodeProvider
    : ISiteMapNodeProvider
{
    private readonly string sourceName = "CustomSiteMapNodeProvider";

    #region ISiteMapNodeProvider Members

    public IEnumerable<ISiteMapNodeToParentRelation> GetSiteMapNodes(ISiteMapNodeHelper helper)
    {
        var result = new List<ISiteMapNodeToParentRelation>();

        using (var db = new DatabaseContextClass())
        {
            foreach (var category in db.Categories.ToList())
            {
                var categoryRelation = this.GetCategoryRelation("Products", category, helper);
                result.Add(categoryRelation);


            }

            foreach (var product in db.Products.Include("Category"))
            {
                var productRelation = this.GetProductRelation("Category_" + product.CategoryId, product, helper);
                result.Add(productRelation);
            }
        }

        return result;
    }

    #endregion

    protected virtual ISiteMapNodeToParentRelation GetCategoryRelation(string parentKey, Category category, ISiteMapNodeHelper helper)
    {
        string key = "Category_" + category.Id;
        var result = helper.CreateNode(key, parentKey, this.sourceName);
        var node = result.Node;

        node.Title = category.Name;

        // Populate other node properties here

        // Important - always set up your routes (including any custom params)
        node.Area = "MyArea"; // Required - set to empty string if not using areas
        node.Controller = "Category"; // Required
        node.Action = "Index"; // Required
        node.RouteValues.Add("id", category.Id.ToString());

        return result;
    }

    protected virtual ISiteMapNodeToParentRelation GetProductRelation(string parentKey, Product product, ISiteMapNodeHelper helper)
    {
        string key = "Product_" + product.Id;
        var result = helper.CreateNode(key, parentKey, this.sourceName);
        var node = result.Node;

        node.Title = product.Name;

        // Populate other node properties here

        // Important - always set up your routes (including any custom params)
        node.Area = "MyArea"; // Required - set to empty string if not using areas
        node.Controller = "Product"; // Required
        node.Action = "Index"; // Required
        node.RouteValues.Add("id", product.Id.ToString());
        node.RouteValues.Add("categoryId", product.CategoryId.ToString()); // Optional - use if you have a many-to-many relationship.

        return result;
    }
}

      

The example above assumes that you have added a node in other ways, which has a key set to "Products" in which all categories will be children. You can of course customize this to suit your needs.

It is usually best to implement this interface only once and use one database connection to load the entire SiteMap. You can always refactor this into multiple classes, each handling one table on your side of the interface to separate concerns. But it's usually best if you put all the key mapping logic between related objects together to make it easier to maintain.

For more examples of how this interface is implemented, see XmlSiteMapNodeProvider and ReflectionSiteMapNodeProvider .

+2


source







All Articles