maartenba / MvcSiteMapProvider

An ASP.NET MVC SiteMapProvider implementation for the ASP.NET MVC framework.
Microsoft Public License
537 stars 220 forks source link

How do I make mvcSiteMapNode to inherit protocol from parent ? #430

Closed tomaskikutis closed 8 years ago

tomaskikutis commented 8 years ago
<?xml version="1.0" encoding="utf-8" ?>
<mvcSiteMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0"
            xsi:schemaLocation="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0 MvcSiteMapSchema.xsd">

  <mvcSiteMapNode title="Home" controller="Home" action="Index" protocol="https">
    <mvcSiteMapNode title="Products" controller="Products" action="Index" />
  </mvcSiteMapNode>

</mvcSiteMap>

This is how my current sitemap looks like. I would like "Products" to inherit https protocol from parent node "Home"

NightOwl888 commented 8 years ago

The only way to do this is to create a custom XmlSiteMapNodeProvider and use external DI to replace the existing one.

Example

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;
    }
}

DI Configuration to Update

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]);
    });
tomaskikutis commented 8 years ago

Thanks for the answer. The solution is quite complicated though, I will stick to adding protocol manually on every node for now.

NightOwl888 commented 8 years ago

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.

tomaskikutis commented 8 years ago

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

NightOwl888 commented 8 years ago

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).

tomaskikutis commented 8 years ago

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.