How to configure multiple sitemaps using using MVC

2019-08-31 07:40发布

I am working on an ASP.NET MVC 4 application that contains multiple areas. As per project requirements each area should have its own sitemap file. From this article: https://github.com/maartenba/MvcSiteMapProvider/wiki/Multiple-Sitemaps-in-One-Application, I understand that in order to make MvcSiteMapProvider work with multiple sitemaps it is necessary to use an external DI. Therefore, I installed the package: MvcSiteMapProvider.MVC4.DI.Unity.Modules and modified the class MvcSiteMapProviderContainerExtension according to this article. Whenever I try to buid a menu the following error is being displayed:

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 help would be greatly appreciated.

If you need further info please let me know.

1条回答
Luminary・发光体
2楼-- · 2019-08-31 08:15

Already answered at https://github.com/maartenba/MvcSiteMapProvider/issues/237. I am copying here for reference.

Here is an example of using 2 different SiteMap instances with Unity.

    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));

    // 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")));
        }
    }

Note that there are factories called SiteMapBuilderFactory, XmlSiteMapNodeProviderFactory, and ReflectionSiteMapNodeProviderFactory that are specific to Unity and other DI containers that don't allow you to insert individual objects without binding to the constructor signature that should be used in this case. This will help ensure minimal number of future changes as the constructor signature is likely to change over time.

查看更多
登录 后发表回答