Closed tomaskikutis closed 8 years ago
The only way to do this is to create a custom XmlSiteMapNodeProvider
and use external DI to replace the existing one.
using MvcSiteMapProvider;
using MvcSiteMapProvider.Builder;
using MvcSiteMapProvider.Xml;
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Xml.Linq;
public class InheritedProtocolXmlSiteMapNodeProvider : XmlSiteMapNodeProvider
{
public InheritedProtocolXmlSiteMapNodeProvider(
bool includeRootNode,
bool useNestedDynamicNodeRecursion,
IXmlSource xmlSource,
ISiteMapXmlNameProvider xmlNameProvider
)
: base(includeRootNode, useNestedDynamicNodeRecursion, xmlSource, xmlNameProvider)
{
}
protected override ISiteMapNodeToParentRelation GetSiteMapNodeFromXmlElement(XElement node, MvcSiteMapProvider.ISiteMapNode parentNode, ISiteMapNodeHelper helper)
{
// Run the base behavior
var nodeParentMap = base.GetSiteMapNodeFromXmlElement(node, parentNode, helper);
// Update the protocol field with inherited value if present
nodeParentMap.Node.Protocol = this.InheritProtocolIfNotProvided(node, parentNode);
// Return the node
return nodeParentMap;
}
protected virtual string InheritProtocolIfNotProvided(XElement node, ISiteMapNode parentNode)
{
var result = node.GetAttributeValue("protocol");
if (node.Attribute("protocol") == null && parentNode != null)
{
result = parentNode.Protocol;
}
return result;
}
}
Here, I am assuming you are using StructureMap, but updating the modules for other DI containers is similar. Default DI configurations (both with a composition root or just a module that you can plugin) for many DI containers can be found on NuGet.
// Register the sitemap node providers
var siteMapNodeProvider = this.For<ISiteMapNodeProvider>().Use<CompositeSiteMapNodeProvider>()
.EnumerableOf<ISiteMapNodeProvider>().Contains(x =>
{
// Replace this line with your custom class (as shown)
x.Type<InheritedProtocolXmlSiteMapNodeProvider>()
.Ctor<bool>("includeRootNode").Is(true)
.Ctor<bool>("useNestedDynamicNodeRecursion").Is(false)
.Ctor<IXmlSource>().Is(xmlSource);
x.Type<ReflectionSiteMapNodeProvider>()
.Ctor<IEnumerable<string>>("includeAssemblies").Is(includeAssembliesForScan)
.Ctor<IEnumerable<string>>("excludeAssemblies").Is(new string[0]);
});
Thanks for the answer. The solution is quite complicated though, I will stick to adding protocol manually on every node for now.
Note that a reasonable solution might be to use the MVC RequireHttpsAttribute (or some custom alternative to it).
Using one of these, you would only need to use relative URLs in MvcSiteMapProvider
.
I'm using IIS redirect to force https and I don't use any URLs in sitemap, only controller and action names as you can see in the code example in my first post https://github.com/maartenba/MvcSiteMapProvider/issues/430#issue-138423439
If IIS is redirecting your URLs before MVC even comes into the picture, why do you need to specify protocol? Relative links will always go to the same protocol, host, and port you are currently on. The only effect of adding protocol in this case is that your URLs will become absolute instead of relative (which could complicate things).
You only need to worry about it if some of the URLs are HTTP and your users are switching back and fourth. But in general, it is recommended to keep SSL enabled throughout the session once the user has hit an HTTPS page (which is why the RequireHttpsAttribute works the way it does).
Yeah, I know that IIS redirects even before MVC gets into play, but for some reason if I don't specify protocol, in sitemap.xml http version is generated. So I just put protocol="*"
on every node, and then it uses what needs to be used - in my case - https.
This is how my current sitemap looks like. I would like "Products" to inherit https protocol from parent node "Home"