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);
Last snippet will not work in CMS12. You have to register partial router in IoC for `IPartialRouter` interface.
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?
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.
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.
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.