Routing question: how to hide all segments before actual page?

Vote:
 

The case is as following:

Given the actual CMS URL - https://website.com/category/article,

I would like to have a possibility to hide the category segment (and all other segments if there are any) and access the "article" only via https://website.com/article URL.

How can I do that?

I've seen documentation at the https://docs.developers.optimizely.com/content-cloud/v12.0.0-content-cloud/docs/routing but not sure which one of the method would be suitable for my need.

Using CMS.12.15.1 and .NET 6.

#296569
Edited, Feb 15, 2023 9:50
Vote:
 

Im guessing you need to implement IPartialRouter : Partial routing (optimizely.com) 

You do need a mechnism to find the page we used Search and Navigation e.g. below 

    public class CaseStudyRouter : IPartialRouter<FolderPage, CaseStudyPage>
    {
        #if DEBUG
        private readonly TimeSpan _cacheTimespan = new TimeSpan(0, 0, 0);
        #else
        private readonly TimeSpan _cacheTimespan = new TimeSpan(0, 30, 0);
        #endif
        private ContentReference _container;

        private string _urlSegment; 

        public CaseStudyRouter()
        {
            var settings = ServiceLocator.Current.GetInstance<ISiteSettingsProvider>();
            _container = settings.Current.CaseStudyContainer;
        }

        public object RoutePartial(FolderPage content, SegmentContext segmentContext)
        {
            if (!content.ContentLink.CompareToIgnoreWorkID(_container))
            {
                return null;
            }

            var nextSegment = segmentContext.GetNextValue(segmentContext.RemainingPath);
            var urlSegment = nextSegment.Next;
            if (string.IsNullOrEmpty(urlSegment))
            {
                return null;
            }

            ArticleBasePage article = null;

            var cacheKey = $"routing-casestudy-{urlSegment}";

            var cache = ServiceLocator.Current.GetInstance<ICache<ArticleBasePage>>();

            _urlSegment = urlSegment; 

            if (cache.IsExists(cacheKey))
            {
                article = cache.Get(cacheKey, GetArticleBasePage);
            }
            else
            {
                article = GetArticleBasePage();
                if(article != null) cache.AddToCache(cacheKey, article, _cacheTimespan);
            }

            if (article != null)
            {
                segmentContext.RemainingPath = nextSegment.Remaining;
                segmentContext.RoutedContentLink = article.ContentLink;
            }

            return article;
        }

        private ArticleBasePage GetArticleBasePage()
        {
            var urlSegment = _urlSegment; 

            ArticleBasePage  article = null; 

            var searchContext = SearchClient.Instance
                .Search<ArticleBasePage>(SearchClient.Instance.Settings.Languages.GetSupportedLanguage("en"))
                .Filter(x => x.URLSegment.MatchCaseInsensitive(urlSegment))
                .Filter(d => d.Ancestors().Match(_container.ID.ToString()))
                .StaticallyCacheFor(this._cacheTimespan)
                .Select(r => r.ContentLink);


            var searchResults = searchContext.GetResult().FirstOrDefault();

            if (!ContentReference.IsNullOrEmpty(searchResults))
            {
                var contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();
                article = contentRepository.Get<ArticleBasePage>(searchResults);
            }

            return article; 
        }

        public PartialRouteData GetPartialVirtualPath(
        CaseStudyPage content,
        string language,
        RouteValueDictionary routeValues,
        RequestContext requestContext)
        {
            var contentLink = requestContext.GetRouteValue("node", routeValues)
                as ContentReference;

            if (!content.ContentLink.CompareToIgnoreWorkID(contentLink))
            {
                return null;
            }

            if (PageEditing.PageIsInEditMode)
            {
                return null;
            }


            return new PartialRouteData
            {
                BasePathRoot = _container,
                PartialVirtualPath = content.URLSegment
            };
        }
    }

And then Register in an InitialzationModule 

    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class RegisterArticleDateRouter : IInitializableModule
    {
        public void Initialize(InitializationEngine context)
        {
            var caseStudyRouter = new CaseStudyRouter();
            RouteTable.Routes.RegisterPartialRouter(caseStudyRouter);
#296571
Feb 15, 2023 11:16
Vote:
 

Last snippet will not work in CMS12. You have to register partial router in IoC for `IPartialRouter` interface.

#296582
Feb 15, 2023 19:56
Minesh Shah (Netcel) - Feb 15, 2023 21:06
Yup missed this in rushing out the reply 😂

See here for better example: https://docs.developers.optimizely.com/content-cloud/v12.0.0-content-cloud/docs/example-of-news-partial-routing
Vote:
 

Thank you for your answers and help, Minesh, and Valdis.

For that specific problem, I used two partial routers:

  • IPartialRouter<FolderPage, PageData>
  • IPartialRouter<StartPage, FolderPage>

Both use the same GetPartialVirtualPath method implementation:

public PartialRouteData GetPartialVirtualPath(FolderPage content, UrlGeneratorContext urlGeneratorContext)
{
    return new PartialRouteData
    {
        BasePathRoot = ContentReference.RootPage.ToReferenceWithoutVersion(), 
        PartialVirtualPath = content.URLSegment
    };
}

And, as for the RoutePartial logic, I'm using Search (as suggested by Minesh):

var supportedLanguage = _find.Settings.Languages.GetSupportedLanguage(language);

var context = _find.Search<SitePageData>(supportedLanguage)
                   .Filter(x => x.Language.Name.MatchCaseInsensitive(language))
                   .Filter(x => x.URLSegment.MatchCaseInsensitive(urlSegmentOfPage))
                   .Take(1)
                   .StaticallyCacheFor(_cacheTimeSpan);

var contentResult = context.GetContentResult();
var page = contentResult.FirstOrDefault();

Also, in the RoutePartial logic, I'm setting segmentContext properties:

segmentContext.RemainingSegments = segmentContext.GetNextSegment().Remaining;
segmentContext.Content = page;

All works ok.

Question - is the above solution proper?

#296709
Edited, Feb 17, 2023 13:07
Vote:
 

I would be a bit wary of using Search and Navigation as part of your routing, even with caching applied. First of all, you're tying the uptime of the site to the uptime of S&N. If S&N hits an issue or you have to clear and rebuild the index, that part of the site will be inaccessible. The other issue would be that, S&N has a rate limit so, when lots of different URLs on your site are being hit in quick succession (for example, when being crawled by a search engine), you risk hitting that rate limit at which point certain URLs will appear to be unavailable. If those URLs appear periodically unavailable to a search crawler, it'll have an adverse affect on your ranking.

Depending on the amount of content which would be surfaced through the partial router, you could maintain the URL -> content reference mapping in memory (perhaps built as part of the site initialisation) though you'd need to manage invalidation/updating of that data when any of the URLs changes.

#296724
Feb 17, 2023 18:37
Vote:
 

Is there a problem with using simple address ?

#296786
Feb 18, 2023 11:38
Vote:
 

Thank you @Paul for notice about S&N. I rewrote logic, so it uses a Content Loader with DDS cache.

@Quan: I use simple address to route some of the Commerce data, but the URL generation is done in IPartialRouters.

#297319
Feb 27, 2023 12:24
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.