November Happy Hour will be moved to Thursday December 5th.

Custom url:s

Vote:
 

Hello,

Current version of EPiServer.CMS = 11.10.6

I have a structure that looks something like figure1.

Each "MyPage" will have a unique code/id in it's "Name in URL" (URLSegment).
This is a multisite multilingual environment.

What I would like to achieve is that all "MyPage" gets a url directly under "ContainerPage1", in this format:
mypage4 = my-domain/ContainerPage1/unique-code-from-mypage4/the-page-name-from-mypage4/
mypage1 = my-domain/ContainerPage1/unique-code-from-mypage1/the-page-name-from-mypage1/


Figure 1

SiteStartPage
- ContainerPage1
-- MyPage1
-- ContainerPage2
--- MyPage2
--- MyPage3
--- ContainerPage3
---- MyPage4
---- MyPage5

SiteStartPage2
...

Tips are gratefully received :)


So far I have been playing around with something like this, it seems to "work" ( the part after /unique-code-from-mypage4/ can be anything in this ), but I'm pretty sure there are better ways and that there are tons of possible problems here ;). Haven't any real experince doing this partial router stuff.

	public class MyPagePartialRouter : IPartialRouter<ContainerNode, MyPage>
	{
	
		...

		private IContentLoader _contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
		private IUrlSegmentGenerator _urlSegmentGenerator = ServiceLocator.Current.GetInstance<IUrlSegmentGenerator>();
		
		
		public object RoutePartial(ContainerNode content, SegmentContext segmentContext)
		{
			...

			var nextSegment = segmentContext.GetNextValue(segmentContext.RemainingPath);
			var theSegmentWithUniqueCode = HttpUtility.UrlDecode(nextSegment.Next);

			var myPageInstanceFound = _contentLoader.GetDescendents(_refefToContainerPage1)
				.Select(x =>
				{
					if (_contentLoader.TryGet<MyPage>(x, out var myPageInstance))
						if (myPageInstance.URLSegment == theSegmentWithUniqueCode)
							return myPageInstance;

					return null;
				})
				.FirstOrDefault(x => x != null);


			if (myPageInstanceFound == null)
				return null;

			segmentContext.RemainingPath = ""; // I had to do like this otherwise I got a 404 
			segmentContext.RoutedContentLink = myPageInstanceFound.ContentLink;

			return myPageInstanceFound;
		}

		public PartialRouteData GetPartialVirtualPath(MyPage content, string language, RouteValueDictionary routeValues, RequestContext requestContext)
		{
			var urlSegment = _urlSegmentGenerator.IsValid(content.URLSegment)
				? content.URLSegment
				: _urlSegmentGenerator.Create(content.URLSegment);

			return new PartialRouteData()
			{
				BasePathRoot = _refefToContainerPage1,
				PartialVirtualPath = string.Format("{0}/{1}/", urlSegment, _urlSegmentGenerator.Create(content.Name))
			};
		}
	}
#222537
Edited, May 07, 2020 6:49
Vote:
 

Hi Richard,

Honestly, if this is what you've got to do, they your approach does solve it.

Depending on how many pages you're going have under ContainerPage1, you may want to look at Episerver Find opposed to using GetDescendants. As the pages stack up there will inevitably be some slowdown, but it really depends on how many pages you're going to have.  You should also look into using the Content Loader/Repository GetItems method (opposed to iterating over items and using the TryGet), it'll likely be more performant as it get's all the previously unloaded contents in 1 DB request.

You don't show how you get _refefToContainerPage1 in the example, but hopefully you're aware that this would be the last routed content in the segment context (segmentContext.RoutedContentLink).

In your implementation here you also don't validate the page name, which is probably desirable. For example:

my-domain.com/ContainerPage1/{mypage4-unique-code}/{any-string}/

Will still load page 4.

Finally, you should be aware that all the old routes will still be valid, so:

my-domain.com/ContainerPage1/ContainerPage2/ContainerPage3/{mypage4-url-segment}

will work. An easy solve would be ensuring the canonical URL is correct.

#222627
May 08, 2020 16:19
Vote:
 

Another thought: it the MyPage type is only going to be used in this structure and you're just assigning some random ID to the URL segment, you could expose the page ID in the URL. e.g.:

my-domain.com/ContainerPage1/{mypage4-id}/{mypage4-name}/

This could easily be converted back into a ContentReference and then you could use your TryGet:

if (!_contentLoader.TryGet<MyPage>(contentLink, out var myPageInstance)) {
    return null;
}

If it's not a MyPage then we know an invalid page ID was fed in.

#222628
May 08, 2020 16:28
Vote:
 

Hi,

Thanx for the response, 

I'm not allowed to use the episerver page id in the url. The customer want the url at least this short and, in their domains, this "friendly" ( in fact, they will probably want the url to be like this : my-domain.com/{mypage4-unique-code}/{page-name-1}/

( Regarding the performance, we will use a custom implementation of Elastic that is used in this project, but I did like this just to make my example simple. We will problably also validate the last part of the url/page.name to make sure it's unique ).


Regarding:

Finally, you should be aware that all the old routes will still be valid, so:

my-domain.com/ContainerPage1/ContainerPage2/ContainerPage3/{mypage4-url-segment}

will work. An easy solve would be ensuring the canonical URL is correct.

Will those url:s ever be generated by episerver?

Thank you very much for you input

br / Richard

#222695
May 11, 2020 7:33
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.