asp.net-mvcautofacmvcsitemapproviderasp.net-mvc-sitemap

MvcSiteMapProvider + Autofac + ISiteMapNodeVisibilityProvider from another assembly


I'm having the toughest time figuring out how to register a custom ISiteMapNodeVisibilityProvider (SiteMapNodeVisibilityProviderBase) using Autofac in MvcSiteMapProvider.

Everything was working fine up until the point that I moved the visibility provider to another assembly. Now, no matter what I try, I always get

The visibility provider instance named 'MyWasWorkingVisibilityProvider, MyNewAssembly' was not found. Check your DI configuration to ensure a visibility provider instance with this name exists and is configured correctly.

According to the MvcSiteMapProvider documentation and code, it appears I need to somehow into the SiteMapNodeVisibilityProviderStrategy... and I think I've done that below... But I'm no Autofac ninja.

In MvcSiteMapProviderModule.cs, I added the new assembly everywhere I could think...

string[] includeAssembliesForScan = new string[] { "MyOldAssembly", "MyNewAssembly" };

var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly, typeof(MyWasWorkingVisibilityProvider).Assembly };

builder.RegisterType<SiteMapNodeVisibilityProviderStrategy>()
    .As<ISiteMapNodeVisibilityProviderStrategy>()
    .WithParameters(new List<Parameter> { new NamedParameter("defaultProviderName", string.Empty), new NamedParameter("siteMapNodeVisibilityProviders", new [] { new MyWasWorkingVisibilityProvider() }) });


builder.RegisterType<MyWasWorkingVisibilityProvider>()
    .As<ISiteMapNodeVisibilityProvider>();

But it still doesn't work.

For what it's worth, the visibility provider for any specific menu item is configured in the database, and the entire menu structure is loaded with a dynamic node provider that is also now in the same assembly as where I've moved the visibility providers. The dynamic node provider is obviously working because it's getting all the way to the point where it's trying to load visibility providers.

I thought https://github.com/maartenba/MvcSiteMapProvider/issues/237 looked helpful, I couldn't get the visibility provider-specific code to compile..

Another example that didn't have any effect: MVC Site Map Provider - SiteMapPath Performance Very Slow?

So I'm stuck now. I'm not a wizard with Autofac OR MvcSiteMap provider, but, like I said, everything was working fine until I moved the visibility provider to another assembly.

Thanks very much for your time and attention! I'm frustrated at this point.


Solution

  • Just a hunch, but I suspect that you didn't update all of the visibilityProvider="MyNamespace.MyVisibilityProvider, MyAssembly" references in your configuration to the new assembly. The SiteMapNodeVisibilityProviderBase uses the .NET full type name to locate the correct type, including the assembly name.

    // From MvcSiteMapProvider.SiteMapNodeVisibilityProviderBase 
    public virtual bool AppliesTo(string providerName)
    {
        if (string.IsNullOrEmpty(providerName))
            return false;
    
        return this.GetType().Equals(Type.GetType(providerName, false));
    }
    

    As for the DI registration, provided you left the first call to CommonConventions.RegisterAllImplementationsOfInterface() in place, you had it right with this line:

    var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly, typeof(MyWasWorkingVisibilityProvider).Assembly };
    

    So the code should look something like this:

    var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly, typeof(MyWasWorkingVisibilityProvider).Assembly };
    var excludeTypes = new Type[] { 
        // Use this array to add types you wish to explicitly exclude from convention-based  
        // auto-registration. By default all types that either match I[TypeName] = [TypeName] or 
        // I[TypeName] = [TypeName]Adapter will be automatically wired up as long as they don't 
        // have the [ExcludeFromAutoRegistrationAttribute].
        //
        // If you want to override a type that follows the convention, you should add the name 
        // of either the implementation name or the interface that it inherits to this list and 
        // add your manual registration code below. This will prevent duplicate registrations 
        // of the types from occurring. 
    
        // Example:
        // typeof(SiteMap),
        // typeof(SiteMapNodeVisibilityProviderStrategy)
    };
    var multipleImplementationTypes = new Type[]  { 
        typeof(ISiteMapNodeUrlResolver), 
        typeof(ISiteMapNodeVisibilityProvider), 
        typeof(IDynamicNodeProvider) 
    };
    
    // Matching type name (I[TypeName] = [TypeName]) or matching type name + suffix Adapter (I[TypeName] = [TypeName]Adapter)
    // and not decorated with the [ExcludeFromAutoRegistrationAttribute].
    CommonConventions.RegisterDefaultConventions(
        (interfaceType, implementationType) => builder.RegisterType(implementationType).As(interfaceType).SingleInstance(),
        new Assembly[] { siteMapProviderAssembly },
        allAssemblies,
        excludeTypes,
        string.Empty);
    
    // Multiple implementations of strategy based extension points (and not decorated with [ExcludeFromAutoRegistrationAttribute]).
    CommonConventions.RegisterAllImplementationsOfInterface(
        (interfaceType, implementationType) => builder.RegisterType(implementationType).As(interfaceType).SingleInstance(),
        multipleImplementationTypes,
        allAssemblies,
        excludeTypes,
        string.Empty);
    
    // Registration of internal controllers
    CommonConventions.RegisterAllImplementationsOfInterface(
        (interfaceType, implementationType) => builder.RegisterType(implementationType).As(interfaceType).AsSelf().InstancePerDependency(),
        new Type[] { typeof(IController) },
        new Assembly[] { siteMapProviderAssembly },
        new Type[0],
        string.Empty);
    

    So, in short your DI configuration is right, but your node configuration of the VisibilityProvider attribute/property is not.

    NOTE: The below line is only for scanning for the [MvcSiteMapNode] attribute on controllers that may not be in the same project as MvcSiteMapProvider, and has nothing to do with the setup of visibility providers.

    string[] includeAssembliesForScan = new string[] { "MyOldAssembly", "MyNewAssembly" };