How to access configuration provider values in asp.net core startup - asp.net-core

In my asp.net core 3.1 project I have setup a custom sql provider which is working great. In my Startup I want to load only the values from the SQLConfigurationProvider into a separate dictionary for use throughout my app. I can see that the configuration object contains a collection of providers as per below screenshot. However I cannot find a way to access only the SQLConfigurationProvider and get the subsequent values. Is this possible?

What you can do is very simple. Once you build your ConfigurationRoot
IConfigurationRoot configRoot = config.Build();
What you can do is to enumerate to a List only the providers you want:
var providers = configRoot.Providers.Where(
p => (p.GetType().Name == "AzureAppConfigurationProvider") ||
(p.GetType().Name == "AzureKeyVaultConfigurationProvider")).ToList();
Then, recreate a new temporary ConfigurationRoot using only the selected providers:
var tempRoot = new ConfigurationRoot(new List<IConfigurationProvider>(providers));
Then, in order to get the values you can do the following:
tempRoot.AsEnumerable().ToDictionary(a => a.Key, a => a.Value)
This way you will get a Dictionary with the key/value pairs of the configuration.
Note: When you initialize a new ConfigurationRoot from the providers, the SDK triggers again the loading of the configuration which might introduce delays (especially if the configuration is retrieved from a cloud service). If you don't want something like that then you probably have to go with the solution suggested by Brando.

According to your description, I suggest you could firstly use Configuration.Providers to get the SQLConfigurationProvider.
But the ConfigurationProvider's data property is protected, so we should write a extension method to get the value and set it into a directory.
More details, you could refer to below codes:
Create a extension class:
public static class ConfigurationProviderExtensions
{
public static HashSet<string> GetFullKeyNames(this IConfigurationProvider provider, string rootKey, HashSet<string> initialKeys)
{
foreach (var key in provider.GetChildKeys(Enumerable.Empty<string>(), rootKey))
{
string surrogateKey = key;
if (rootKey != null)
{
surrogateKey = rootKey + ":" + key;
}
GetFullKeyNames(provider, surrogateKey, initialKeys);
if (!initialKeys.Any(k => k.StartsWith(surrogateKey)))
{
initialKeys.Add(surrogateKey);
}
}
return initialKeys;
}
}
Then you could add below codes to get the provider and get the value..
// Replace the EnvironmentVariablesConfigurationProvider to your provider
var re = ((ConfigurationRoot)Configuration).Providers.FirstOrDefault(x=>x.GetType() == typeof(EnvironmentVariablesConfigurationProvider));
var directory = new Dictionary<string, string>();
foreach (var key in re.GetFullKeyNames(null, new HashSet<string>()).OrderBy(p => p))
{
if (re.TryGet(key, out var value))
{
directory.Add(key, value);
}
}
Result:

Related

Change name of cshtml file in ASP.NET Core RazorPages

My environment: ASP.NET Core 5 with RazorPages, Webpack 5.
In razor pages (.cshtml) that reference svg files, I want to inline them. This is something Webpack can do (via a plugin), but I'm not sure how to integrate these two tech stacks.
I could write templatised cshtml files, and populate them via webpack:
ContactUs.cshtml.cs
ContactUs.cshtml <------ read by webpack
ContactUs.generated.cshtml <------ generated by webpack
But then how do I force msbuild / aspnet to use the generated file (ContactUs.generated.cshtml) instead of the template file (ContactUs.cshtml) when building?
I suspect the answer is to use IPageRouteModelConvention but I'm unsure how.
(A dirty workaround is to instead use the filenames ContactUs.template.cshtml and ContactUs.cshtml but I prefer something like the above, as "generated" is clearer.)
UPDATE
To simplify the problem:
The compiler looks for Foo.cshtml.cs and Foo.cshtml.
How do I tell it to instead look for Foo.cshtml.cs and Foo.generated.cshtml?
When loading the app, the framework loads for you a set of PageRouteModels which is auto-generated from the razor page folders (by convention). Each such model contains a set of SelectorModel each one of which has an AttributeRouteModel. What you need to do is just modify that AttributeRouteModel.Template by removing the suffixed part from the auto-generated value.
You can create a custom IPageRouteModelConvention to target each PageRouteModel. However that way you cannot ensure the routes from being duplicated (because after modifying the AttributeRouteModel.Template, it may become duplicate with some other existing route). Unless you have to manage a shared set of route templates. Instead you can create a custom IPageRouteModelProvider. It provides all the PageRouteModels in one place so that you can modify & add or remove any. This way it's so convenient that you can support 2 razor pages in which one page is more prioritized over the other (e.g: you have Index.cshtml and Index.generated.cshtml and you want it to pick Index.generated.cshtml. If that generated view is not existed, the default Index.cshtml will be used).
So here is the detailed code:
public class SuffixedNamePageRouteModelProvider : IPageRouteModelProvider
{
public SuffixedNamePageRouteModelProvider(string pageNameSuffix, int order = 0)
{
_pageNameSuffixPattern = string.IsNullOrEmpty(pageNameSuffix) ? "" : $"\\.{Regex.Escape(pageNameSuffix)}$";
Order = order;
}
readonly string _pageNameSuffixPattern;
public int Order { get; }
public void OnProvidersExecuted(PageRouteModelProviderContext context)
{
}
public void OnProvidersExecuting(PageRouteModelProviderContext context)
{
if(_pageNameSuffixPattern == "") return;
var suffixedRoutes = context.RouteModels.Where(e => Regex.IsMatch(e.ViewEnginePath, _pageNameSuffixPattern)).ToList();
var overriddenRoutes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var route in suffixedRoutes)
{
//NOTE: this is not required to help it pick the right page we want.
//But it's necessary for other related code to work properly (e.g: link generation, ...)
//we need to update the "page" route data as well
route.RouteValues["page"] = Regex.Replace(route.RouteValues["page"], _pageNameSuffixPattern, "");
var overriddenRoute = Regex.Replace(route.ViewEnginePath, _pageNameSuffixPattern, "");
var isIndexRoute = overriddenRoute.EndsWith("/index", StringComparison.OrdinalIgnoreCase);
foreach (var selector in route.Selectors.Where(e => e.AttributeRouteModel?.Template != null))
{
var template = Regex.Replace(selector.AttributeRouteModel.Template, _pageNameSuffixPattern, "");
if (template != selector.AttributeRouteModel.Template)
{
selector.AttributeRouteModel.Template = template;
overriddenRoutes.Add($"/{template.TrimStart('/')}");
selector.AttributeRouteModel.SuppressLinkGeneration = isIndexRoute;
}
}
//Add another selector for routing to the same page from another path.
//Here we add the root path to select the index page
if (isIndexRoute)
{
var defaultTemplate = Regex.Replace(overriddenRoute, "/index$", "", RegexOptions.IgnoreCase);
route.Selectors.Add(new SelectorModel()
{
AttributeRouteModel = new AttributeRouteModel() { Template = defaultTemplate }
});
}
}
//remove the overridden routes to avoid exception of duplicate routes
foreach (var route in context.RouteModels.Where(e => overriddenRoutes.Contains(e.ViewEnginePath)).ToList())
{
context.RouteModels.Remove(route);
}
}
}
Register the IPageRouteModelProvider in Startup.ConfigureServices:
services.AddSingleton<IPageRouteModelProvider>(new SuffixedNamePageRouteModelProvider("generated"));

ABP: Rebuilding Localization Sources from Custom Provider

I am using ABP v4.9.0 (.NET CORE 2.2) with angular client
I built some custom localization providers. These providers get translation dictionaries from an external API.
I add localization sources on startup with these providers.
var customProvider = new CustomLocalizationProvider(...);
var localizationSource = new DictionaryBasedLocalizationSource("SOURCENAME", customProvider );
config.Localization.Sources.Add(localizationSource );
On startup, the providers InitializeDictionaries() is called and localization dictionaries are built.
So far, so good, working as intended.
Now i'd like to manually Reload these translations on demand, but I can't make this working.
Here is what I tried.
Here I trigger the re-synchronize of the language ressources:
foreach (var localizationSource in _localizationConfiguration.Sources)
{
try
{
localizationSource.Initialize(_localizationConfiguration, _iocResolver);
}
catch (Exception e)
{
Logger.Warn($"Could not get Localization Data for source '{localizationSource.Name}'", e);
}
}
In the custom provider, I first clear the Dictionaries
public class CustomLocalizationProvider : LocalizationDictionaryProviderBase
{
protected int IterationNo = 0;
protected override void InitializeDictionaries()
{
Dictionaries.Clear();
IterationNo += 1;
var deDict = new LocalizationDictionary(new CultureInfo("de-DE"));
deDict["HelloWorld"] = $"Hallo Welt Nummer {IterationNo}";
Dictionaries.Add("de-DE", deDict);
var enDict = new LocalizationDictionary(new CultureInfo("en"));
enDict["HelloWorld"] = $"Hello World number {IterationNo}";
Dictionaries.Add("en", enDict);
}
}
The provider is executed again as expected.
But when I eventually use the localization clientside (angular), I still get the original translations.
What am I missing?
Thanks for the help.
In the meanwhile I had to go for another approach.
I am now using a XmlEmbeddedFileLocalizationDictionaryProvider wrapped by a MultiTenantLocalizationDictionaryProvider.
This way, I am using db-localizations with xml-sources as fallback
Then I manually load the ressources from my API in some appservice. These localizations are then updated in the database by using LanguageTextManager.UpdateStringAsync().

How to dynamically resolve controller with endpoint routing?

Upgrading to asp.net core 2.2 in my hobby project there is a new routing system I want to migrate to. Previously I implemented a custom IRouter to be able to set the controller for the request dynamically. The incoming request path can be anything. I match the request against a database table containing slugs and it looks up the a matching data container class type for the resolved slug. After that I resolve a controller type that can handle the request and set the RouteData values to the current HttpContext and passing it along to the default implementation for IRouter and everything works ok.
Custom implementaion of IRouter:
public async Task RouteAsync(RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
var page = _pIndex.GetPage(requestPath);
if (page != null)
{
var controllerType = _controllerResolver.GetController(page.PageType);
if (controllerType != null)
{
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
newRouteData.Values["pageType"] = page.PageType;
newRouteData.Values["controller"] = controllerType.Name.Replace("Controller", "");
newRouteData.Values["action"] = "Index";
context.RouteData = newRouteData;
await _defaultRouter.RouteAsync(context);
}
}
}
A controller to handle a specific page type.
public class SomePageController : PageController<PageData>
{
public ActionResult Index(PageData currentPage)
{
return View("Index", currentPage);
}
}
However I got stuck when I'm trying to figure out how I can solve it using the new system. I'm not sure where I'm suppose to extend it for this behavior. I don't want to turn off the endpoint routing feature because I see an opportunity to learn something. I would aso appreciate a code sample if possible.
In ASP.NET 3.0 there is an new dynamic controller routing system. You can implement DynamicRouteValueTransformer.
Documentation is on the way, look at the github issue

How to resolve a Collection of Types from within the IoC Container

We're using MvvmCross in our app, and using the MvxSimpleIoCContainer
In the app startup, we register all of our Migrations.
it's easy do do since all migrations inherit from IMigration
typeof (IMigration)
.Assembly
.CreatableTypes()
.Inherits<IMigration>()
.AsTypes()
.RegisterAsLazySingleton();
After the migrations are registered, we need to run them consecutively, and therefore the MigrationRunner looks a little something like this.
Mvx.Resolve<IMigrationRunner>().RunAll(SystemRole.Client, new List<IMigration>
{
Mvx.IocConstruct<Migration001>(),
Mvx.IocConstruct<Migration002>()
});
as you can see, I'm explicitely constructing each Migration using Mvx. This get's tedious and is prone to mistakes when a bunch of migrations end up in the app.
What I'd prefer to be able to do is resolve the entire collection in one fell swoop, and not have to touch it every time I create a new Migration.
Is there a way to do this via MvvmCross?
Pseudo Code
Mvx.Resolve<IMigrationRunner>()
.RunAll(SystemRole.Client, Mvx.ResolveAll<IMigration>());
I would use LINQ to get the list of types. Unfortunately there's no way to get a list of registered types, so you'll have to enumerate the types again like you do for registration. You can even sort by type name. Now that you have a list of types, you can create a new list of instantiated/resolved types to pass into RunAll(). Something like:
var migrationTypes = typeof (IMigration)
.Assembly
.CreatableTypes()
.Inherits<IMigration>()
.AsTypes()
.OrderBy(t => t.Name)
.ToList();
Mvx.Resolve<IMigrationRunner>()
.RunAll(SystemRole.Client,
migrationTypes.Select(t => Mvx.Resolve(t)).ToList());
This is "browser" code, so no guarantees, but you get the gist.
Ok, so reflection is the answer to this problem for now, and eventually, I'd like to either extend our custom MvxServiceLocator : IServiceLocator to include something like
public IEnumerable<object> GetAllInstances(Type serviceType){...}
but for now I've just got a RunMigrations() method in the app
private void RunMigrations()
{
var migrationType = typeof (IMigration); // IMigration is in a separate assembly
var migrations = GetType().Assembly
.GetTypes()
.Where(
t => migrationType.IsAssignableFrom(t) && !t.IsAbstract)
.OrderBy(t => t.Name)
.Select(m => _serviceLocator.GetInstance(m) as IMigration)
.ToList();
var migrationRunner = new MigrationRunner(Mvx.Resolve<IDbProvider>());
migrationRunner.RunAll(SystemRole.Client, migrations);
}
where _serviceLocator.GetInstance(m) just lives in our custom MvxServiceLocator
public object GetInstance(Type serviceType)
{
return _ioCProvider.Resolve(serviceType);
}
Edit: here's how I extended our service locator wrapper.
public class MvxServiceLocator : IServiceLocator
{
private readonly IMvxIoCProvider _ioCProvider;
public MvxServiceLocator(IMvxIoCProvider ioCProvider)
{
_ioCProvider = ioCProvider;
}
public IEnumerable<TService> GetAllInstances<TService>()
{
var serviceType = typeof(TService);
var registrations = GetType().Assembly
.GetTypes()
.Where(
t => serviceType.IsAssignableFrom(t) && !t.IsAbstract)
.Select(m => (TService)_ioCProvider.Resolve(m));
return registrations;
}
}

How to implement custom SiteMapNodeProvider

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.
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:
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).
IDynamicNodeProvider doesn't need to be part of the DI setup because it is already processed by both XmlSiteMapNodeProvider and ReflectionSiteMapNodeProvider.
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.