How to implement your own SiteMapNodeProvider

I'm trying to adapt the MvcSiteMapProvider to create a palette based on some information stored in a database.

The answer in this posted promisingly, so I implemented my own SiteMapNodeProvider. But then I didn't know how to connect everything, so the new implemented SiteMapNodeProvider is used instead of the static xml file ("Mvc.sitemap").

As I use SimpleInjector in my project, I named the install method in the already existing initialization code Injection.

 public static void Initialize()
    {
        Injection.Global = new Container();
        InitializeContainer(Injection.Global);
        Injection.Global.RegisterMvcControllers(Assembly.GetExecutingAssembly());
        Injection.Global.RegisterMvcAttributeFilterProvider();
        Injection.Global.Verify();
        DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(Injection.Global));
    }

    private static void InitializeContainer(Container container)
    {
        // Setup configuration of DI
        MvcSiteMapProviderContainerInitializer.SetUp(container);

        //... register some other stuff for my project here ...
    }

      

The MvcSiteMapProviderContainerInitializer class is created by the package: 'Mvcsitemapprovider.mvc4.di.simpleinjector / 4.4.5'

Does anyone know what to do to get my project to use the newly created SiteMapNodeProvider? I couldn't find any documentation about this in the official doc ...

edit: I tried what you suggested (even removed the old DI stuff and only used the one from the nuget package) but still I get errors ... here is what I have in my MvcSiteMapProviderContainerInitializer

    public static void SetUp(Container container)
        {
            bool securityTrimmingEnabled = false;
            bool enableLocalization = true;
            string absoluteFileName = HostingEnvironment.MapPath("~/Mvc.sitemap");
            TimeSpan absoluteCacheExpiration = TimeSpan.FromMinutes(5);
            string[] includeAssembliesForScan = new string[] { "testsitemap" };

// Extension to allow resolution of arrays by GetAllInstances (natively based on IEnumerable).
// source from: https://simpleinjector.codeplex.com/wikipage?title=CollectionRegistrationExtensions
            AllowToResolveArraysAndLists(container);

            var currentAssembly = typeof(MvcSiteMapProviderContainerInitializer).Assembly;
            var siteMapProviderAssembly = typeof(SiteMaps).Assembly;
            var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly };
            var excludeTypes = new Type[]
                {
                    typeof (SiteMapNodeVisibilityProviderStrategy),
                    typeof (SiteMapXmlReservedAttributeNameProvider),
                    typeof (SiteMapBuilderSetStrategy),
                    typeof (ControllerTypeResolverFactory),

// Added 2013-06-28 by eric-b to avoid default singleton registration:
                    typeof(XmlSiteMapController),

// Added 2013-06-28 by eric-b for SimpleInjector.Verify method:
                    typeof(PreservedRouteParameterCollection),
                    typeof(MvcResolver),
                    typeof(MvcSiteMapProvider.SiteMap),
                    typeof(MetaRobotsValueCollection),
                    typeof(RoleCollection),
                    typeof(SiteMapPluginProvider),
                    typeof(ControllerTypeResolver),
                    typeof(RouteValueDictionary),
                    typeof(AttributeDictionary)

                    ,typeof(SiteMapNodeCreator)
                };
            var multipleImplementationTypes = new Type[]
                {
                    typeof (ISiteMapNodeUrlResolver),
                    typeof (ISiteMapNodeVisibilityProvider),
                    typeof (IDynamicNodeProvider)
                };

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

// Multiple implementations of strategy based extension points
            CommonConventions.RegisterAllImplementationsOfInterfaceSingle(
                (interfaceType, implementationTypes) => container.RegisterAll(interfaceType, implementationTypes),
                multipleImplementationTypes,
                allAssemblies,
                new Type[0],
                "^Composite");

            container.Register<XmlSiteMapController>();

// Visibility Providers
            container.RegisterSingle<ISiteMapNodeVisibilityProviderStrategy>(() =>
                                                                       new SiteMapNodeVisibilityProviderStrategy(
                                                                           container.GetAllInstances
                                                                               <ISiteMapNodeVisibilityProvider>().
                                                                               ToArray(), string.Empty));

// Pass in the global controllerBuilder reference
            container.RegisterSingle<ControllerBuilder>(() => ControllerBuilder.Current);

            container.RegisterSingle<IControllerBuilder, ControllerBuilderAdaptor>();

            container.RegisterSingle<IBuildManager, BuildManagerAdaptor>();

            container.RegisterSingle<IControllerTypeResolverFactory>(() =>
                                                               new ControllerTypeResolverFactory(new string[0],
                                                                                                 container.GetInstance
                                                                                                     <IControllerBuilder
                                                                                                     >(),
                                                                                                 container.GetInstance
                                                                                                     <IBuildManager>()));

// Configure Security
            container.RegisterAll<IAclModule>(typeof(AuthorizeAttributeAclModule), typeof(XmlRolesAclModule));
            container.RegisterSingle<IAclModule>(() => new CompositeAclModule(container.GetAllInstances<IAclModule>().ToArray()));

// Setup cache




            container.RegisterSingle<System.Runtime.Caching.ObjectCache>(() => System.Runtime.Caching.MemoryCache.Default);
            container.RegisterSingleOpenGeneric(typeof(ICacheProvider<>), typeof(RuntimeCacheProvider<>));
            container.RegisterSingle<ICacheDependency>(() => new RuntimeFileCacheDependency(absoluteFileName));

            container.RegisterSingle<ICacheDetails>(() => new CacheDetails(absoluteCacheExpiration, TimeSpan.MinValue, container.GetInstance<ICacheDependency>()));

// Configure the visitors
            container.RegisterSingle<ISiteMapNodeVisitor, UrlResolvingSiteMapNodeVisitor>();


// Prepare for the sitemap node providers
            container.RegisterSingle<ISiteMapXmlReservedAttributeNameProvider>(
                () => new SiteMapXmlReservedAttributeNameProvider(new string[0]));

            container.RegisterSingle<IXmlSource>(() => new FileXmlSource(absoluteFileName));


            // Register the sitemap node providers
            container.RegisterSingle<XmlSiteMapNodeProvider>(() => container.GetInstance<XmlSiteMapNodeProviderFactory>()
                .Create(container.GetInstance<IXmlSource>()));
            container.RegisterSingle<ReflectionSiteMapNodeProvider>(() => container.GetInstance<ReflectionSiteMapNodeProviderFactory>()
                .Create(includeAssembliesForScan));

            // Register your custom sitemap node provider
            container.RegisterSingle<ISiteMapNodeProvider, CustomSiteMapNodeProvider>();

            // Register the collection of sitemap node providers (including the custom one)
            container.RegisterSingle<ISiteMapBuilder>(() => container.GetInstance<SiteMapBuilderFactory>()
                .Create(new CompositeSiteMapNodeProvider(
                    container.GetInstance<XmlSiteMapNodeProvider>(),
                    container.GetInstance<ReflectionSiteMapNodeProvider>(),
                    container.GetInstance<CustomSiteMapNodeProvider>())));


            container.RegisterAll<ISiteMapBuilderSet>(ResolveISiteMapBuilderSets(container, securityTrimmingEnabled, enableLocalization));
            container.RegisterSingle<ISiteMapBuilderSetStrategy>(() => new SiteMapBuilderSetStrategy(container.GetAllInstances<ISiteMapBuilderSet>().ToArray()));
        }

        private static IEnumerable<ISiteMapBuilderSet> ResolveISiteMapBuilderSets(Container container, bool securityTrimmingEnabled, bool enableLocalization)
        {
            yield return new SiteMapBuilderSet(
                "default",
                securityTrimmingEnabled,
                enableLocalization,
                container.GetInstance<ISiteMapBuilder>(),
                container.GetInstance<ICacheDetails>());
        }

        private static void AllowToResolveArraysAndLists(Container container)
        {
            container.ResolveUnregisteredType += (sender, e) =>
            {
                var serviceType = e.UnregisteredServiceType;

                if (serviceType.IsArray)
                {
                    RegisterArrayResolver(e, container,
                        serviceType.GetElementType());
                }
                else if (serviceType.IsGenericType &&
                    serviceType.GetGenericTypeDefinition() == typeof(IList<>))
                {
                    RegisterArrayResolver(e, container,
                        serviceType.GetGenericArguments()[0]);
                }
            };
        }

        private static void RegisterArrayResolver(UnregisteredTypeEventArgs e, Container container, Type elementType)
        {
            var producer = container.GetRegistration(typeof(IEnumerable<>)
                .MakeGenericType(elementType));
            var enumerableExpression = producer.BuildExpression();
            var arrayMethod = typeof(Enumerable).GetMethod("ToArray")
                .MakeGenericMethod(elementType);
            var arrayExpression = Expression.Call(arrayMethod, enumerableExpression);
            e.Register(arrayExpression);
        }
    }

      

but still I am getting the following exception:

There is no registration for the DynamicSiteMapNodeBuilder type. implicit registration could not be done. Constructor The DynamicSiteMapNodeBuilder type contains a parameter of type ISiteMapNodeCreator named 'siteMapNodeCreator' that is not registered. Make sure the ISiteMapNodeCreator is registered with the container or modify the DynamicSiteMapNodeBuilder constructor.

0


source to share


1 answer


First of all, to integrate with your existing DI installation, you must install MvcSiteMapProvider.MVC4.DI.SimpleInjector.Modules

instead MvcSiteMapProvider.MVC4.DI.SimpleInjector

. You can downgrade by running this command from the Package Manager Console:

PM> Uninstall-Package -Id MvcSiteMapProvider.MVC4.DI.SimpleInjector

Make sure NOT to remove any dependencies. This ensures that you don't have two sets of DI init code in your project - there should only be 1 for the entire application.

Then you need to connect to the DI as well as other initialization code required by the MvcSiteMapProvider. the readme file contains instructions on how to do this. This is how you could do it with your existing configuration.

public static void Initialize()
{
    Injection.Global = new Container();
    InitializeContainer(Injection.Global);
    Injection.Global.RegisterMvcControllers(Assembly.GetExecutingAssembly());
    Injection.Global.RegisterMvcAttributeFilterProvider();
    Injection.Global.Verify();
    DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(Injection.Global));
}

private static void InitializeContainer(Container container)
{
    // Setup configuration of DI (required)
    MvcSiteMapProviderContainerInitializer.SetUp(container);

    // Setup global sitemap loader (required)
    MvcSiteMapProvider.SiteMaps.Loader = container.GetInstance<ISiteMapLoader>();

    // Check all configured .sitemap files to ensure they follow the XSD for MvcSiteMapProvider (optional)
    var validator = container.GetInstance<ISiteMapXmlValidator>();
    validator.ValidateXml(HostingEnvironment.MapPath("~/Mvc.sitemap"));

    // Register the Sitemaps routes for search engines (optional)
    XmlSiteMapController.RegisterRoutes(RouteTable.Routes); // NOTE: You can put this in your RouteConfig.cs file if desired.

    //... register some other stuff for your project here ...
}

      

If the endpoint is /sitemap.xml

not working, you may also need to add this line to register the XmlSiteMapController:

Injection.Global.RegisterMvcControllers(typeof(MvcSiteMapProvider.SiteMaps).Assembly);

      

To implement ISiteMapNodeProvider there is an example here: MvcSiteMapProvider ISiteMapBuilder combined with IDynamicNodeProvider .

To register your own ISiteMapNodeProvider, you just need to make sure it is added to the SiteMapBuilder constructor. You can also exclude existing SiteMapNodeProviders from the code below depending on your needs.



// Register the sitemap node providers
container.RegisterSingle<XmlSiteMapNodeProvider>(() => container.GetInstance<XmlSiteMapNodeProviderFactory>()
    .Create(container.GetInstance<IXmlSource>()));
container.RegisterSingle<ReflectionSiteMapNodeProvider>(() => container.GetInstance<ReflectionSiteMapNodeProviderFactory>()
    .Create(includeAssembliesForScan));

// Register your custom sitemap node provider
container.RegisterSingle<ISiteMapNodeProvider, CustomSiteMapNodeProvider>();

// Register the collection of sitemap node providers (including the custom one)
container.RegisterSingle<ISiteMapBuilder>(() => container.GetInstance<SiteMapBuilderFactory>()
    .Create(new CompositeSiteMapNodeProvider(
        container.GetInstance<XmlSiteMapNodeProvider>(), 
        container.GetInstance<ReflectionSiteMapNodeProvider>(), 
        container.GetInstance<CustomSiteMapNodeProvider>())));

      

Note that IDynamicNodeProvider (which is documented) does almost the same thing as ISiteMapNodeProvider, so you can use this parameter. There are 3 main differences:

  • With IDynamicNodeProvider, you have to create a node "template" that defines the dynamicNodeProvider attribute, and the node template itself will not be included in the SiteMap, so it must be used in conjunction with an ISiteMapNodeProvider implementation that handles dynamic nodes (the built-in ISiteMapNodeProviders do this automatically).
  • The IDynamicNodeProvider does not have to be part of the DI setup as it is already handled by both the XmlSiteMapNodeProvider and ReflectionSiteMapNodeProvider.
  • With ISiteMapNodeProvider, you work directly with the ISiteMapNode object, with the IDynamicNodeProvider with which you work with the abstraction (DynamicNodeProvider), and the automatic conversion takes place.

About SimpleInjector.Verify

If you want this to Verify()

work, you need to add the following to the excludeTypes array in the MvcSiteMapProviderContainerInitializer.

typeof(SiteMapNodeCreator),
typeof(DynamicSiteMapNodeBuilder)

      

I added them to a module and will be in the next version of the Nuget package, but these modules are not updating, so you need to do it manually.

Note that the method Verify()

tries to instantiate everything that is registered with the container, including objects that are never created by the container in the real world. Therefore, if you use a method Verify()

, you need to be more diligent so that something is not accidentally registered. This makes it difficult to perform rule-based registrations.

+1


source







All Articles