How to implement custom SiteMapNodeProvider

2019-01-20 18:50发布

I am trying to adapt the MvcSiteMapProvider to create the breadcrumb based on some Information stored in a database.

The answer in this post sounded promising so i implemented my own SiteMapNodeProvider. But then i didnt know how to wire things up so the newly implemented SiteMapNodeProvider is used instead of the static xml file ("Mvc.sitemap").

As i am using SimpleInjector in my project, i called the setup method in my already existent Injection-initialization code.

 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 got created by the package: 'Mvcsitemapprovider.mvc4.di.simpleinjector/4.4.5'

Does anybody know what to do to make my project use the newly created SiteMapNodeProvider? I couldnt find any documentation about this in the official docu...

edit: i tried what you suggested (even removed the old DI stuff and only used the one from the nuget-package) but still i am getting 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 get the following exception:

No registration for type DynamicSiteMapNodeBuilder could be found and an implicit registration could not be made. The constructor of the type DynamicSiteMapNodeBuilder contains the parameter of type ISiteMapNodeCreator with name 'siteMapNodeCreator' that is not registered. Please ensure ISiteMapNodeCreator is registered in the container, or change the constructor of DynamicSiteMapNodeBuilder.

1条回答
欢心
2楼-- · 2019-01-20 19:17

First of all, to integrate with an existing DI setup, you should install MvcSiteMapProvider.MVC4.DI.SimpleInjector.Modules instead of MvcSiteMapProvider.MVC4.DI.SimpleInjector. You can downgrade by running this command from package manager console:

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

Be sure NOT to uninstall any dependencies. This will ensure that you don't have 2 sets of DI initialization code in your project - there should only be 1 for the entire application.

Next, you need to wire up for DI as well as some other initialization code required by MvcSiteMapProvider. The readme file contains instructions how to do this. Here is how you would 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 /sitemap.xml endpoint doesn't work, 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 in conjunction with IDynamicNodeProvider.

To register your custom ISiteMapNodeProvider, you just need to ensure it gets added to the constructor of SiteMapBuilder. You can also exclude the 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>())));

Do note that IDynamicNodeProvider (which is documented) does almost exactly the same thing as ISiteMapNodeProvider, so you could use that option instead. There are 3 main differences:

  1. With IDynamicNodeProvider, you must create a "template" node that defines the dynamicNodeProvider attribute, and the template node itself won't be included in the SiteMap, so it must be used in conjunction with a ISiteMapNodeProvider implementation that processes the dynamic nodes (the built-in ISiteMapNodeProviders do this automatically).
  2. IDynamicNodeProvider doesn't need to be part of the DI setup because it is already processed by both XmlSiteMapNodeProvider and ReflectionSiteMapNodeProvider.
  3. With ISiteMapNodeProvider, you are working directly with the ISiteMapNode object, with IDynamicNodeProvider you are working with an abstraction (DynamicNodeProvider) and there is a conversion that happens automatically.

About SimpleInjector.Verify

If you want Verify() to work, you need to add the following to the excludeTypes array in the MvcSiteMapProviderContainerInitializer.

typeof(SiteMapNodeCreator),
typeof(DynamicSiteMapNodeBuilder)

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

Note that the Verify() method tries to create an instance of everything that is registered with the container - including objects that never get created by the container in the real world. Therefore, if you use the Verify() method you have to be more diligent that something is not accidentally registered. This makes convention-based registration more difficult to do.

查看更多
登录 后发表回答