Custom segment in the middle of URL

Vote:
 

Hi,

I'm attemping to add a custom segment in the middle of an url. Consider the two urls:

/products/consumer/bikes and
/products/business/bikes

I would like both urls to route to the same page in Episerver CMS.

The cms page structure is:

  • Products
    • Bikes 

The consumer and business part of the url is added to the RouteData object in the request context. I believe I have the correct logic for this implemented in a custom segment class that extends the SegmentBase class.

However, what URL should I use when registering this custom segment to the route table? I have tried {language}/{node}/{customertype}/{partial}/{action} but it does not seem to match the mentioned urls to the Bikes-page.

   RouteTable.Routes.MapContentRoute(
                name: "CustomerTypeSegment",
                url: "{language}/{node}/{customertype}/{partial}/{action}",
                defaults: new { action = "index" },
                parameters: parameters
            );

The normal behaviour of the Episerver pages route will render the bike-page when hitting /products/bikes, however I am looking for the same behaviour using /products/consumer/bikes or /products/business/bikes. Is this even possible to do using only url segments? Or do I need to implement a custom PartialRouter?

#206750
Aug 29, 2019 13:57
Vote:
 

Hi!

From your route mapping, it looks like you should get a match on /products/bikes/consumer and /products/bikes/business. The {node} part here is actually /products/bikes. I'm no expert on this topic but I tested to intercept the default IUrlSegmentRouter and successfully managed to route to the correct page using a similar scenario. See below for example:

public class CustomerTypeUrlSegmentRouter : IUrlSegmentRouter
{
    private readonly IUrlSegmentRouter _defaultUrlSegmentRouter;

    public CustomerTypeUrlSegmentRouter(IUrlSegmentRouter defaultUrlSegmentRouter)
    {
        _defaultUrlSegmentRouter = defaultUrlSegmentRouter;
    }

    public Func<SiteDefinition, ContentReference> RootResolver
    {
        get => _defaultUrlSegmentRouter.RootResolver;
        set => _defaultUrlSegmentRouter.RootResolver = value;
    }

    public ContentReference ResolveContentForIncoming(ContentReference contentReference, string urlSegment,
        SegmentContext segmentContext)
    {
        var contentLink = _defaultUrlSegmentRouter.ResolveContentForIncoming(contentReference, urlSegment, segmentContext);

        // If we have hit an unknown segment and it qualifies as a customer type segment.
        if (contentLink == null && IsCustomerTypeSegmentMatch(urlSegment))
        {
            // TODO: Maybe validate if incoming content link is actually the products root page.

            // Return the incoming content link, in your case "Products".
            return contentReference;
        }

        return contentLink;
    }

    public ContentReference ResolveContentForOutgoing(ContentReference contentLink)
    {
        return _defaultUrlSegmentRouter.ResolveContentForOutgoing(contentLink);
    }

    public IRoutable GetRoutingSegment(ContentReference contentLink, string preferredLanguageBranch)
    {
        return _defaultUrlSegmentRouter.GetRoutingSegment(contentLink, preferredLanguageBranch);
    }

    public bool IsContentUnderRoot(ContentReference contentLink)
    {
        return _defaultUrlSegmentRouter.IsContentUnderRoot(contentLink);
    }

    private bool IsCustomerTypeSegmentMatch(string urlSegment)
    {
        // This logic should probably be a bit more advanced. :)
        return urlSegment == "consumer" || urlSegment == "business";
    }
}

You need to intercept the default segment router. This can be done in an initialization module (you probably already have a dependency injection module):

[ModuleDependency(typeof(ServiceContainerInitialization))]
[InitializableModule]
public class DependencyResolverInitialization : IConfigurableModule
{
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.Services.Intercept<IUrlSegmentRouter>((locator, defaultUrlSegmentRouter) =>
            new CustomerTypeUrlSegmentRouter(defaultUrlSegmentRouter));
    }

    public void Initialize(InitializationEngine context)
    {
    }

    public void Uninitialize(InitializationEngine context)
    {
    }
}
#206962
Sep 04, 2019 15:57
Vote:
 

Hi Mattias and thank you for your detailed answer!

You are correct in pointing out the {node} in this scenario ends up being "/products/bikes/consumer", while I really need "/products/consumer/bikes" instead. I naively tried using a url of "{language}/{node}/{customertype}/{node}/{partial}/{action}" to support this but naturally this didn't work. It appears that segments in-between a node-"path" make things a bit more complicated. Instead of going the segmentbase route I have instead re-implemented the default episerver partial router, looking for customer-type segments in the url and manually stripping it out, and from there looking up the content in the CMS page hierarchy. I had to do this for the commerce router as well. This feels quite intrusive so a simpler method would be awesome.

#206982
Sep 05, 2019 10:37
Vote:
 

Hi,

Why don't use shortcut page?

// Ha Bui

#206996
Sep 06, 2019 10:25