Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MvcSiteMapProvider with multiple sitemaps #237

Closed
UncleZen opened this issue Oct 15, 2013 · 14 comments

Comments

@UncleZen
Copy link

@UncleZen UncleZen commented Oct 15, 2013

I have been trying to implement the MVCSiteMapProvider on a ASP.NET MVC 4 application I am working on based on this article using Unity 3 but I have been receiving this error message.

There is more than one node declared without a parent key. The parent key must be set for all but 1 node in the SiteMap. The node with no parent key will be considered the root node. Note that when defining nodes in XML, the XML file must contain the root node.

You can disable XML configuration by setting the MvcSiteMapProvider_EnableSiteMapFile setting to "false". For an external DI configuration, you can disable XML parsing by removing the XmlSiteMapNodeProvider from the MvcSiteMapProvider DI module.

Alternatively, you can set the MvcSiteMapProvider_IncludeRootNodeFromSiteMapFile setting to "false" to exclude the root node from the XML file, but include all of the other nodes. For an external DI configuration, this setting can be found on the constructor of the XmlSiteMapNodeProvider.

SiteMapCacheKey: 'sitemap://admin/'

Ambiguous Root Nodes:

ParentKey: '' | Controller: 'Home' | Action: 'Index' | Area: '' | URL: '/' | Key: 'rootarea' | Source: '.sitemap XML File'

ParentKey: '' | Controller: 'AdminHome' | Action: 'Index' | Area: 'Admin' | URL: '/Admin/AdminHome' | Key: 'adminarea' | Source: '.sitemap XML File'

Any idea how to fix this?

Thanks!

Zen

@NightOwl888

This comment has been minimized.

Copy link
Collaborator

@NightOwl888 NightOwl888 commented Oct 15, 2013

As the error states, you can only have one root node per sitemap.

Curious, though - if defining them in XML the parentKey is usually inferred automatically. This could be a bug having to do with areas. You should be able to work around the problem by setting the key and parent key explicitly on the nodes.

<mvcSiteMapNode title="Home" controller="Home" action="Index" key="HomePage">
    <mvcSiteMapNode title="AdminHome" area="Admin" controller="AdminHome" action="Index" key="adminarea" parentKey="HomePage">
</mvcSiteMapNode>

Are you defining all of the nodes in the same XML file? If not, this is really the only solution - explicitly set the key on the home page and explicitly set the ParentKey on the area node.

Note that creating multiple files doesn't necessarily mean you will generate more than one SiteMap instance - it is possible to add the nodes from multiple XML files to the same SiteMap. To actually create more than one SiteMap instance per application, there are some additional steps required: https://github.com/maartenba/MvcSiteMapProvider/wiki/Multiple-Sitemaps-in-One-Application

@UncleZen

This comment has been minimized.

Copy link
Author

@UncleZen UncleZen commented Oct 15, 2013

I have defined the nodes in XML files. Currently there are to different sitemap files. One places in the root of the web application and the other in the root of the Admin area.

Following the article you mentioned above I tried to create multiple SiteMap instances by first installing the MvcSiteMapProvider.MVC4.DI.Unity.Modules package and modifying the class MvcSiteMapProviderContainerExtension. But that did not help.

    public class MvcSiteMapProviderContainerExtension
        : UnityContainerExtension
    {
        protected override void Initialize()
        {
            bool securityTrimmingEnabled = false;
            bool enableLocalization = true;

            string rootSiteMapFile = HostingEnvironment.MapPath("~/Mvc.sitemap");
            string adminSiteMapFile = HostingEnvironment.MapPath("~/Areas/Admin/Mvc.sitemap");



            TimeSpan absoluteCacheExpiration = TimeSpan.FromMinutes(5);
            string[] includeAssembliesForScan = new string[] { "MccSiteMapProviderTest" };

            var currentAssembly = this.GetType().Assembly;
            var siteMapProviderAssembly = typeof(SiteMaps).Assembly;
            var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly };
            var excludeTypes = new Type[] {
                typeof(SiteMapNodeVisibilityProviderStrategy),
                typeof(SiteMapXmlReservedAttributeNameProvider),
                typeof(SiteMapBuilderSetStrategy),
                typeof(SiteMapNodeUrlResolverStrategy),
                typeof(DynamicNodeProviderStrategy)
            };
            var multipleImplementationTypes = new Type[] {
                typeof(ISiteMapNodeUrlResolver),
                typeof(ISiteMapNodeVisibilityProvider),
                typeof(IDynamicNodeProvider)
            };

// Single implementations of interface with matching name (minus the "I").
            CommonConventions.RegisterDefaultConventions(
                (interfaceType, implementationType) => this.Container.RegisterType(interfaceType, implementationType, new ContainerControlledLifetimeManager()),
                new Assembly[] { siteMapProviderAssembly },
                allAssemblies,
                excludeTypes,
                string.Empty);

// Multiple implementations of strategy based extension points
            CommonConventions.RegisterAllImplementationsOfInterface(
                (interfaceType, implementationType) => this.Container.RegisterType(interfaceType, implementationType, implementationType.Name, new ContainerControlledLifetimeManager()),
                multipleImplementationTypes,
                allAssemblies,
                excludeTypes,
                "^Composite");

// TODO: Find a better way to inject an array constructor

// Url Resolvers
            this.Container.RegisterType<ISiteMapNodeUrlResolverStrategy, SiteMapNodeUrlResolverStrategy>(new InjectionConstructor(
                new ResolvedArrayParameter<ISiteMapNodeUrlResolver>(this.Container.ResolveAll<ISiteMapNodeUrlResolver>().ToArray())
                ));

// Visibility Providers
            this.Container.RegisterType<ISiteMapNodeVisibilityProviderStrategy, SiteMapNodeVisibilityProviderStrategy>(new InjectionConstructor(
                new ResolvedArrayParameter<ISiteMapNodeVisibilityProvider>(this.Container.ResolveAll<ISiteMapNodeVisibilityProvider>().ToArray()),
                new InjectionParameter<string>(string.Empty)
                ));

// Dynamic Node Providers
            this.Container.RegisterType<IDynamicNodeProviderStrategy, DynamicNodeProviderStrategy>(new InjectionConstructor(
                new ResolvedArrayParameter<IDynamicNodeProvider>(this.Container.ResolveAll<IDynamicNodeProvider>().ToArray())
                ));


// Pass in the global controllerBuilder reference
            this.Container.RegisterInstance<ControllerBuilder>(ControllerBuilder.Current);
            this.Container.RegisterType<IControllerBuilder, ControllerBuilderAdaptor>(new PerResolveLifetimeManager());

            this.Container.RegisterType<IBuildManager, BuildManagerAdaptor>(new PerResolveLifetimeManager());

            this.Container.RegisterType<IControllerTypeResolverFactory, ControllerTypeResolverFactory>(new InjectionConstructor(
                new List<string>(),
                new ResolvedParameter<IControllerBuilder>(),
                new ResolvedParameter<IBuildManager>()));

// Configure Security

// IMPORTANT: Must give arrays of object a name in Unity in order for it to resolve them.
            this.Container.RegisterType<IAclModule, AuthorizeAttributeAclModule>("authorizeAttribute");
            this.Container.RegisterType<IAclModule, XmlRolesAclModule>("xmlRoles");
            this.Container.RegisterType<IAclModule, CompositeAclModule>(new InjectionConstructor(new ResolvedArrayParameter<IAclModule>(
                new ResolvedParameter<IAclModule>("authorizeAttribute"),
                new ResolvedParameter<IAclModule>("xmlRoles"))));



            this.Container.RegisterType<ISiteMapCacheKeyGenerator, SiteMapCacheKeyGenerator2>();


            this.Container.RegisterInstance<System.Runtime.Caching.ObjectCache>(System.Runtime.Caching.MemoryCache.Default);
            this.Container.RegisterType(typeof(ICacheProvider<>), typeof(RuntimeCacheProvider<>));

            this.Container.RegisterType<ICacheDependency, RuntimeFileCacheDependency>(
                "rootSiteMapCacheDependency", new InjectionConstructor(rootSiteMapFile));

            this.Container.RegisterType<ICacheDependency, RuntimeFileCacheDependency>(
                "adminSiteMapCacheDependency", new InjectionConstructor(adminSiteMapFile));

            this.Container.RegisterType<ICacheDetails, CacheDetails>("rootSiteMapCacheDetails",
                new InjectionConstructor(absoluteCacheExpiration, TimeSpan.MinValue, new ResolvedParameter<ICacheDependency>("rootSiteMapCacheDependency")));

            this.Container.RegisterType<ICacheDetails, CacheDetails>("adminSiteMapCacheDetails",
                new InjectionConstructor(absoluteCacheExpiration, TimeSpan.MinValue, new ResolvedParameter<ICacheDependency>("adminSiteMapCacheDependency")));

// Configure the visitors
            this.Container.RegisterType<ISiteMapNodeVisitor, UrlResolvingSiteMapNodeVisitor>();

// Prepare for the sitemap node providers
            this.Container.RegisterType<IXmlSource, FileXmlSource>("rootSiteMapXmlSource", new InjectionConstructor(rootSiteMapFile));
            this.Container.RegisterType<IXmlSource, FileXmlSource>("adminSiteMapXmlSource", new InjectionConstructor(adminSiteMapFile));

            this.Container.RegisterType<ISiteMapXmlReservedAttributeNameProvider, SiteMapXmlReservedAttributeNameProvider>(new InjectionConstructor(new List<string>()));

// IMPORTANT: Must give arrays of object a name in Unity in order for it to resolve them.
// Register the sitemap node providers
            this.Container.RegisterInstance<ISiteMapNodeProvider>("rootXmlSiteMapNodeProvider",
                this.Container.Resolve<XmlSiteMapNodeProviderFactory>().Create(this.Container.Resolve<IXmlSource>("rootSiteMapXmlSource")), new ContainerControlledLifetimeManager());

            this.Container.RegisterInstance<ISiteMapNodeProvider>("adminXmlSiteMapNodeProvider",
                this.Container.Resolve<XmlSiteMapNodeProviderFactory>().Create(this.Container.Resolve<IXmlSource>("adminSiteMapXmlSource")), new ContainerControlledLifetimeManager());

            this.Container.RegisterInstance<ISiteMapNodeProvider>("ReflectionSiteMapNodeProvider1",
                this.Container.Resolve<ReflectionSiteMapNodeProviderFactory>().Create(includeAssembliesForScan), new ContainerControlledLifetimeManager());
            this.Container.RegisterType<ISiteMapNodeProvider, CompositeSiteMapNodeProvider>(new InjectionConstructor(new ResolvedArrayParameter<ISiteMapNodeProvider>(
                new ResolvedParameter<ISiteMapNodeProvider>("rootXmlSiteMapNodeProvider"),
                new ResolvedParameter<ISiteMapNodeProvider>("adminXmlSiteMapNodeProvider"),
                new ResolvedParameter<ISiteMapNodeProvider>("ReflectionSiteMapNodeProvider1"))));

// Configure the builders
            this.Container.RegisterType<ISiteMapBuilder, SiteMapBuilder>();

// Configure the builder sets
            this.Container.RegisterType<ISiteMapBuilderSet, SiteMapBuilderSet>("rootBuilderSet",
                new InjectionConstructor(
                    "default",
                    securityTrimmingEnabled,
                    enableLocalization,
                    new ResolvedParameter<ISiteMapBuilder>(),
                    new ResolvedParameter<ICacheDetails>("rootSiteMapCacheDetails")));

            this.Container.RegisterType<ISiteMapBuilderSet, SiteMapBuilderSet>("adminBuilderSet",
                new InjectionConstructor(
                    "admin",
                    securityTrimmingEnabled,
                    enableLocalization,
                    new ResolvedParameter<ISiteMapBuilder>(),
                    new ResolvedParameter<ICacheDetails>("adminSiteMapCacheDetails")));

            this.Container.RegisterType<ISiteMapBuilderSetStrategy, SiteMapBuilderSetStrategy>(new InjectionConstructor(new ResolvedArrayParameter<ISiteMapBuilderSet>(
                                new ResolvedParameter<ISiteMapBuilderSet>("rootBuilderSet")
                                , new ResolvedParameter<ISiteMapBuilderSet>("adminBuilderSet"))));
        }
    }

Is there a way to email you the files I have changed to make this work?

@NightOwl888

This comment has been minimized.

Copy link
Collaborator

@NightOwl888 NightOwl888 commented Oct 15, 2013

Configuring in Unity is a bit trickier than StructureMap in that you must create "named" instances of objects. If you have a choice, I would recommend using a different DI container. Autofac, Ninject, StructureMap, and Windsor have better maintainability (at least with MvcSiteMapProvider) because they do not tightly couple the configuration to a specific constructor signature. But if you have too much invested in Unity already you are better off sticking with it.

If you post your entire MvcSiteMapProviderContainerExtension here, I would bet that the source of your problem can be resolved. To post code, you just need to fence it with 3 backtick marks before and after it. You can also specify the language it is to color the code appropriately.

```csharp
// Code here
//``` <-- place this on the next line without the leading // marks

There is also a complete guide how to use the GitHub Flavored Markdown syntax.

@UncleZen

This comment has been minimized.

Copy link
Author

@UncleZen UncleZen commented Oct 15, 2013

Thank you very much for such a prompt reply. I just realized that I have missed one step. Namely I forgot to implement a custom BuilderSetMapper. I'll fix this first and get back to you.

@UncleZen

This comment has been minimized.

Copy link
Author

@UncleZen UncleZen commented Oct 15, 2013

I am still facing the same issue. Here is the code:

MvcSiteMapProviderContainerExtension

public class MvcSiteMapProviderContainerExtension
        : UnityContainerExtension
    {
        protected override void Initialize()
        {
            bool securityTrimmingEnabled = false;
            bool enableLocalization = true;

            string rootSiteMapFile = HostingEnvironment.MapPath("~/Mvc.sitemap");
            string adminSiteMapFile = HostingEnvironment.MapPath("~/Areas/Admin/Mvc.sitemap");



            TimeSpan absoluteCacheExpiration = TimeSpan.FromMinutes(5);
            string[] includeAssembliesForScan = new string[] { "MccSiteMapProviderTest" };

            var currentAssembly = this.GetType().Assembly;
            var siteMapProviderAssembly = typeof(SiteMaps).Assembly;
            var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly };
            var excludeTypes = new Type[] {
                typeof(SiteMapNodeVisibilityProviderStrategy),
                typeof(SiteMapXmlReservedAttributeNameProvider),
                typeof(SiteMapBuilderSetStrategy),
                typeof(SiteMapNodeUrlResolverStrategy),
                typeof(DynamicNodeProviderStrategy)
            };
            var multipleImplementationTypes = new Type[] {
                typeof(ISiteMapNodeUrlResolver),
                typeof(ISiteMapNodeVisibilityProvider),
                typeof(IDynamicNodeProvider)
            };

// Single implementations of interface with matching name (minus the "I").
            CommonConventions.RegisterDefaultConventions(
                (interfaceType, implementationType) => this.Container.RegisterType(interfaceType, implementationType, new ContainerControlledLifetimeManager()),
                new Assembly[] { siteMapProviderAssembly },
                allAssemblies,
                excludeTypes,
                string.Empty);

// Multiple implementations of strategy based extension points
            CommonConventions.RegisterAllImplementationsOfInterface(
                (interfaceType, implementationType) => this.Container.RegisterType(interfaceType, implementationType, implementationType.Name, new ContainerControlledLifetimeManager()),
                multipleImplementationTypes,
                allAssemblies,
                excludeTypes,
                "^Composite");

// TODO: Find a better way to inject an array constructor

// Url Resolvers
            this.Container.RegisterType<ISiteMapNodeUrlResolverStrategy, SiteMapNodeUrlResolverStrategy>(new InjectionConstructor(
                new ResolvedArrayParameter<ISiteMapNodeUrlResolver>(this.Container.ResolveAll<ISiteMapNodeUrlResolver>().ToArray())
                ));

// Visibility Providers
            this.Container.RegisterType<ISiteMapNodeVisibilityProviderStrategy, SiteMapNodeVisibilityProviderStrategy>(new InjectionConstructor(
                new ResolvedArrayParameter<ISiteMapNodeVisibilityProvider>(this.Container.ResolveAll<ISiteMapNodeVisibilityProvider>().ToArray()),
                new InjectionParameter<string>(string.Empty)
                ));

// Dynamic Node Providers
            this.Container.RegisterType<IDynamicNodeProviderStrategy, DynamicNodeProviderStrategy>(new InjectionConstructor(
                new ResolvedArrayParameter<IDynamicNodeProvider>(this.Container.ResolveAll<IDynamicNodeProvider>().ToArray())
                ));


// Pass in the global controllerBuilder reference
            this.Container.RegisterInstance<ControllerBuilder>(ControllerBuilder.Current);
            this.Container.RegisterType<IControllerBuilder, ControllerBuilderAdaptor>(new PerResolveLifetimeManager());

            this.Container.RegisterType<IBuildManager, BuildManagerAdaptor>(new PerResolveLifetimeManager());

            this.Container.RegisterType<IControllerTypeResolverFactory, ControllerTypeResolverFactory>(new InjectionConstructor(
                new List<string>(),
                new ResolvedParameter<IControllerBuilder>(),
                new ResolvedParameter<IBuildManager>()));

// Configure Security

// IMPORTANT: Must give arrays of object a name in Unity in order for it to resolve them.
            this.Container.RegisterType<IAclModule, AuthorizeAttributeAclModule>("authorizeAttribute");
            this.Container.RegisterType<IAclModule, XmlRolesAclModule>("xmlRoles");
            this.Container.RegisterType<IAclModule, CompositeAclModule>(new InjectionConstructor(new ResolvedArrayParameter<IAclModule>(
                new ResolvedParameter<IAclModule>("authorizeAttribute"),
                new ResolvedParameter<IAclModule>("xmlRoles"))));



            this.Container.RegisterType<ISiteMapCacheKeyGenerator, SiteMapCacheKeyGenerator2>();
            this.Container.RegisterType<ISiteMapCacheKeyToBuilderSetMapper, CustomSiteMapCacheKeyToBuilderSetMapper>();


            this.Container.RegisterInstance<System.Runtime.Caching.ObjectCache>(System.Runtime.Caching.MemoryCache.Default);
            this.Container.RegisterType(typeof(ICacheProvider<>), typeof(RuntimeCacheProvider<>));

            this.Container.RegisterType<ICacheDependency, RuntimeFileCacheDependency>(
                "rootSiteMapCacheDependency", new InjectionConstructor(rootSiteMapFile));

            this.Container.RegisterType<ICacheDependency, RuntimeFileCacheDependency>(
                "adminSiteMapCacheDependency", new InjectionConstructor(adminSiteMapFile));

            this.Container.RegisterType<ICacheDetails, CacheDetails>("rootSiteMapCacheDetails",
                new InjectionConstructor(absoluteCacheExpiration, TimeSpan.MinValue, new ResolvedParameter<ICacheDependency>("rootSiteMapCacheDependency")));

            this.Container.RegisterType<ICacheDetails, CacheDetails>("adminSiteMapCacheDetails",
                new InjectionConstructor(absoluteCacheExpiration, TimeSpan.MinValue, new ResolvedParameter<ICacheDependency>("adminSiteMapCacheDependency")));

// Configure the visitors
            this.Container.RegisterType<ISiteMapNodeVisitor, UrlResolvingSiteMapNodeVisitor>();

// Prepare for the sitemap node providers
            this.Container.RegisterType<IXmlSource, FileXmlSource>("rootSiteMapXmlSource", new InjectionConstructor(rootSiteMapFile));
            this.Container.RegisterType<IXmlSource, FileXmlSource>("adminSiteMapXmlSource", new InjectionConstructor(adminSiteMapFile));

            this.Container.RegisterType<ISiteMapXmlReservedAttributeNameProvider, SiteMapXmlReservedAttributeNameProvider>(new InjectionConstructor(new List<string>()));

// IMPORTANT: Must give arrays of object a name in Unity in order for it to resolve them.
// Register the sitemap node providers
            this.Container.RegisterInstance<ISiteMapNodeProvider>("rootXmlSiteMapNodeProvider",
                this.Container.Resolve<XmlSiteMapNodeProviderFactory>().Create(this.Container.Resolve<IXmlSource>("rootSiteMapXmlSource")), new ContainerControlledLifetimeManager());

            this.Container.RegisterInstance<ISiteMapNodeProvider>("adminXmlSiteMapNodeProvider",
                this.Container.Resolve<XmlSiteMapNodeProviderFactory>().Create(this.Container.Resolve<IXmlSource>("adminSiteMapXmlSource")), new ContainerControlledLifetimeManager());

            this.Container.RegisterInstance<ISiteMapNodeProvider>("ReflectionSiteMapNodeProvider1",
                this.Container.Resolve<ReflectionSiteMapNodeProviderFactory>().Create(includeAssembliesForScan), new ContainerControlledLifetimeManager());
            this.Container.RegisterType<ISiteMapNodeProvider, CompositeSiteMapNodeProvider>(new InjectionConstructor(new ResolvedArrayParameter<ISiteMapNodeProvider>(
                new ResolvedParameter<ISiteMapNodeProvider>("rootXmlSiteMapNodeProvider"),
                new ResolvedParameter<ISiteMapNodeProvider>("adminXmlSiteMapNodeProvider"),
                new ResolvedParameter<ISiteMapNodeProvider>("ReflectionSiteMapNodeProvider1"))));

// Configure the builders
            this.Container.RegisterType<ISiteMapBuilder, SiteMapBuilder>();

// Configure the builder sets
            this.Container.RegisterType<ISiteMapBuilderSet, SiteMapBuilderSet>("rootBuilderSet",
                new InjectionConstructor(
                    "default",
                    securityTrimmingEnabled,
                    enableLocalization,
                    new ResolvedParameter<ISiteMapBuilder>(),
                    new ResolvedParameter<ICacheDetails>("rootSiteMapCacheDetails")));

            this.Container.RegisterType<ISiteMapBuilderSet, SiteMapBuilderSet>("adminBuilderSet",
                new InjectionConstructor(
                    "admin",
                    securityTrimmingEnabled,
                    enableLocalization,
                    new ResolvedParameter<ISiteMapBuilder>(),
                    new ResolvedParameter<ICacheDetails>("adminSiteMapCacheDetails")));

            this.Container.RegisterType<ISiteMapBuilderSetStrategy, SiteMapBuilderSetStrategy>(new InjectionConstructor(new ResolvedArrayParameter<ISiteMapBuilderSet>(
                                new ResolvedParameter<ISiteMapBuilderSet>("rootBuilderSet")
                                , new ResolvedParameter<ISiteMapBuilderSet>("adminBuilderSet"))));
        }
    }

SiteMapCacheKeyGenerator2

public class SiteMapCacheKeyGenerator2 : ISiteMapCacheKeyGenerator
    {
        #region ISiteMapCacheKeyGenerator Members

        public SiteMapCacheKeyGenerator2(
            IMvcContextFactory mvcContextFactory
            )
        {
            if (mvcContextFactory == null)
                throw new ArgumentNullException("mvcContextFactory");
            this.mvcContextFactory = mvcContextFactory;
        }

        protected readonly IMvcContextFactory mvcContextFactory;



        public virtual string GenerateKey()
        {
            var routeData = mvcContextFactory.CreateUrlHelper().RequestContext.RouteData;
            object val = null;
            var area = "root";

            if(routeData.DataTokens.TryGetValue("area", out val))
            {
                area = val as string;
                if (area != null)
                {
                    area = area.ToLower();
                }
            }

            var cacheKey = string.Concat("sitemap://", area, "/");
            return cacheKey;
        }

        #endregion
    }

CustomSiteMapCacheKeyToBuilderSetMapper

public class CustomSiteMapCacheKeyToBuilderSetMapper
        : ISiteMapCacheKeyToBuilderSetMapper
    {
        #region ISiteMapCacheKeyToBuilderSetMapper Members

        public string GetBuilderSetName(string cacheKey)
        {
            switch (cacheKey)
            {
                case "sitemap://root/":
                    return "default";
                case "sitemap://admin/":
                    return "admin";
                default:
                    return "default";
            }

        }

        #endregion
    }

Mvc.sitemap

<?xml version="1.0" encoding="utf-8" ?>
<mvcSiteMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0"
            xsi:schemaLocation="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0 MvcSiteMapSchema.xsd" >

  <mvcSiteMapNode title="Home" controller="Home" action="Index" >
      <mvcSiteMapNode title="About" controller="Home" action="About"  />
      <mvcSiteMapNode title="Contact" controller="Home" action="Contact"  />
  </mvcSiteMapNode>

</mvcSiteMap>

Admin Area Mvc.sitemap

<?xml version="1.0" encoding="utf-8" ?>
<mvcSiteMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0"
            xsi:schemaLocation="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0 MvcSiteMapSchema.xsd" >

  <mvcSiteMapNode title="Admin Home" controller="AdminHome" action="Index" area="Admin" >
      <mvcSiteMapNode title="Admin Home" controller="AdminHome" action="ShowAll" area="Admin" />

  </mvcSiteMapNode>

</mvcSiteMap>
@NightOwl888

This comment has been minimized.

Copy link
Collaborator

@NightOwl888 NightOwl888 commented Oct 15, 2013

Your issue is due to the fact you have not separated the providers all the way up to the SiteMapBuilderSet, but you instead have combined the SiteMapNodeProviders into a single unit that is shared between SiteMapBuilderSet instances.

// IMPORTANT: Must give arrays of object a name in Unity in order for it to resolve them.
// Register the sitemap node providers
    this.Container.RegisterInstance<ISiteMapNodeProvider>("rootXmlSiteMapNodeProvider",
        this.Container.Resolve<XmlSiteMapNodeProviderFactory>().Create(this.Container.Resolve<IXmlSource>("rootSiteMapXmlSource")), new ContainerControlledLifetimeManager());

    this.Container.RegisterInstance<ISiteMapNodeProvider>("adminXmlSiteMapNodeProvider",
        this.Container.Resolve<XmlSiteMapNodeProviderFactory>().Create(this.Container.Resolve<IXmlSource>("adminSiteMapXmlSource")), new ContainerControlledLifetimeManager());

    this.Container.RegisterInstance<ISiteMapNodeProvider>("ReflectionSiteMapNodeProvider1",
        this.Container.Resolve<ReflectionSiteMapNodeProviderFactory>().Create(includeAssembliesForScan), new ContainerControlledLifetimeManager());
    this.Container.RegisterType<ISiteMapNodeProvider, CompositeSiteMapNodeProvider>(new InjectionConstructor(new ResolvedArrayParameter<ISiteMapNodeProvider>(
        new ResolvedParameter<ISiteMapNodeProvider>("rootXmlSiteMapNodeProvider"),
        new ResolvedParameter<ISiteMapNodeProvider>("adminXmlSiteMapNodeProvider"),
        new ResolvedParameter<ISiteMapNodeProvider>("ReflectionSiteMapNodeProvider1"))));

// Configure the builders
    this.Container.RegisterType<ISiteMapBuilder, SiteMapBuilder>();

// Configure the builder sets
    this.Container.RegisterType<ISiteMapBuilderSet, SiteMapBuilderSet>("rootBuilderSet",
        new InjectionConstructor(
            "default",
            securityTrimmingEnabled,
            enableLocalization,
            new ResolvedParameter<ISiteMapBuilder>(), // You are sharing the same builder instance here
            new ResolvedParameter<ICacheDetails>("rootSiteMapCacheDetails")));

    this.Container.RegisterType<ISiteMapBuilderSet, SiteMapBuilderSet>("adminBuilderSet",
        new InjectionConstructor(
            "admin",
            securityTrimmingEnabled,
            enableLocalization,
            new ResolvedParameter<ISiteMapBuilder>(), // You are sharing the same builder instance here
            new ResolvedParameter<ICacheDetails>("adminSiteMapCacheDetails")));

Instead of the above, it should be something like...

// IMPORTANT: Must give arrays of object a name in Unity in order for it to resolve them.
// Register the sitemap node providers
    this.Container.RegisterInstance<ISiteMapNodeProvider>("rootXmlSiteMapNodeProvider",
        this.Container.Resolve<XmlSiteMapNodeProviderFactory>().Create(this.Container.Resolve<IXmlSource>("rootSiteMapXmlSource")), new ContainerControlledLifetimeManager());

    this.Container.RegisterInstance<ISiteMapNodeProvider>("adminXmlSiteMapNodeProvider",
        this.Container.Resolve<XmlSiteMapNodeProviderFactory>().Create(this.Container.Resolve<IXmlSource>("adminSiteMapXmlSource")), new ContainerControlledLifetimeManager());

    this.Container.RegisterInstance<ISiteMapNodeProvider>("ReflectionSiteMapNodeProvider1",
        this.Container.Resolve<ReflectionSiteMapNodeProviderFactory>().Create(includeAssembliesForScan), new ContainerControlledLifetimeManager());

    this.Container.RegisterType<ISiteMapNodeProvider, CompositeSiteMapNodeProvider>("rootSiteMapNodeProvider", 
        new ContainerControlledLifetimeManager(),
        new InjectionConstructor(new ResolvedArrayParameter<ISiteMapNodeProvider>(
            new ResolvedParameter<ISiteMapNodeProvider>("rootXmlSiteMapNodeProvider"),
            new ResolvedParameter<ISiteMapNodeProvider>("ReflectionSiteMapNodeProvider1"))));

    this.Container.RegisterType<ISiteMapNodeProvider, CompositeSiteMapNodeProvider>("adminSiteMapNodeProvider",
        new ContainerControlledLifetimeManager(),
        new InjectionConstructor(new ResolvedArrayParameter<ISiteMapNodeProvider>(
            new ResolvedParameter<ISiteMapNodeProvider>("adminXmlSiteMapNodeProvider"),
            new ResolvedParameter<ISiteMapNodeProvider>("ReflectionSiteMapNodeProvider1"))));

// Configure the builders
    this.Container.RegisterInstance<ISiteMapBuilder>("rootSiteMapBuilder", 
        this.Container.Resolve<SiteMapBuilderFactory>().Create(this.Container.Resolve<ISiteMapNodeProvider>("rootSiteMapNodeProvider")), 
        new ContainerControlledLifetimeManager());

    this.Container.RegisterInstance<ISiteMapBuilder>("adminSiteMapBuilder", 
        this.Container.Resolve<SiteMapBuilderFactory>().Create(this.Container.Resolve<ISiteMapNodeProvider>("adminSiteMapNodeProvider")), 
        new ContainerControlledLifetimeManager());      

// Configure the builder sets
    this.Container.RegisterType<ISiteMapBuilderSet, SiteMapBuilderSet>("rootBuilderSet",
        new InjectionConstructor(
            "default",
            securityTrimmingEnabled,
            enableLocalization,
            new ResolvedParameter<ISiteMapBuilder>("rootSiteMapBuilder"),
            new ResolvedParameter<ICacheDetails>("rootSiteMapCacheDetails")));

    this.Container.RegisterType<ISiteMapBuilderSet, SiteMapBuilderSet>("adminBuilderSet",
        new InjectionConstructor(
            "admin",
            securityTrimmingEnabled,
            enableLocalization,
            new ResolvedParameter<ISiteMapBuilder>("adminSiteMapBuilder"),
            new ResolvedParameter<ICacheDetails>("adminSiteMapCacheDetails")));

Do note there are a couple of different options for setting up MvcSiteMapNodeAttribute so it will work with multiple sitemaps - you can do it like shown above by sharing the same ReflectionSiteMapNodeProvider instance and then use the SiteMapNodeAttribute.SiteMapCacheKey property to let MvcSiteMapProvider know which SiteMap instance the node belongs to. Option 2 is to declare 2 different ReflectionSiteMapNodeProvider instances and use 2 different assemblies, specifying them in the corresponding "includeAssembliesForScan" parameter. Of course, if you don't plan to use MvcSiteMapNodeAttribute, you can simply remove ReflectionSiteMapNodeProvider from the configuration.

@UncleZen

This comment has been minimized.

Copy link
Author

@UncleZen UncleZen commented Oct 15, 2013

Thank you very much for your efforts. I will give it a try tomorrow and I'll let you know if there are any issues.

@UncleZen

This comment has been minimized.

Copy link
Author

@UncleZen UncleZen commented Oct 16, 2013

It worked like a charm. Thank you very much for your helpful tips too.

@NightOwl888

This comment has been minimized.

Copy link
Collaborator

@NightOwl888 NightOwl888 commented Oct 17, 2013

Glad to hear you got it working.

@sparra

This comment has been minimized.

Copy link

@sparra sparra commented Nov 27, 2013

@UncleZen Could you kindly provide a sample of your view's code for using the different site maps? I'm using the same strategy with autofac and always return the 'default' sitemap when trying @Html.MvcSiteMap("admin").SiteMap(). Thank you.

@NightOwl888

This comment has been minimized.

Copy link
Collaborator

@NightOwl888 NightOwl888 commented Nov 28, 2013

@sparra - The default SiteMapCacheKeyToBuilderSetMapper always returns "default" - you must implement ISiteMapCacheKeyToBuilderSetMapper yourself to override this behavior. Take care not to put any complex business logic in there, though - it should always return the same value for each cacheKey that is passed in. If you need a more complex scheme, then implement ISiteMapCacheKeyGenerator and put your logic there.

Be sure to also see the documentation in the wiki: https://github.com/maartenba/MvcSiteMapProvider/wiki/Multiple-Sitemaps-in-One-Application

@sparra

This comment has been minimized.

Copy link

@sparra sparra commented Nov 28, 2013

@NightOwl888 Thank you very much. I was missing such impl, now my DI injection works properly.

@szmulder

This comment has been minimized.

Copy link

@szmulder szmulder commented Jun 23, 2014

To be honest, the way to implement multiple sitemaps is over complicated, you guy should make it simply

@UncleZen

This comment has been minimized.

Copy link
Author

@UncleZen UncleZen commented Oct 17, 2014

I should check my notifications more often. @sparra my apologies. @NightOwl888 thanks for helping him.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.