In last project I had to prepare a custom Partial Router. The part of the site consists of news home page which contains news articles. But there is also additional container page used by editors to group content. The scenario was very simple – the container page shouldn’t be displayed in URL.
I used the example from EPiServer 9 documentation. My case was very similar so I could reuse almost all of the sample code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
public class ArticlesPartialRouter : IPartialRouter<NewHome, Article> { private readonly IContentRepository _contentRepository; public ArticlesPartialRouter(IContentRepository contentRepository) { this._contentRepository = contentRepository; } public object RoutePartial(NewsHome content, SegmentContext segmentContext) { var articleContainer = this._contentRepository.GetChildren<NewsArticlesContainer>(content.ContentLink).FirstOrDefault(); if (articleContainer == null) { return null; } var nextSegment = segmentContext.GetNextValue(segmentContext.RemainingPath); var articleName = HttpUtility.UrlDecode(nextSegment.Next); var article = this._contentRepository.GetChildren<Article>(articleContainer.ContentLink) .FirstOrDefault(a => a.Name == articleName); if (article == null) { return null; } segmentContext.RemainingPath = nextSegment.Remaining; return article; } public PartialRouteData GetPartialVirtualPath(Article content, string language, RouteValueDictionary routeValues, RequestContext requestContext) { return new PartialRouteData() { BasePathRoot = Configuration.Instance.NewsHomeContentLink, PartialVirtualPath = string.Format("{0}/", HttpUtility.UrlPathEncode(content.Name)) }; } } |
Current page issue
After I run the application and tried open any article I get exception.
The stack trace:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
[InvalidCastException: ContentData Cast. The requested type Alloy.Models.News.Article is not compatible with Castle.Proxies.NewsHomeProxy] EPiServer.Core.ContentDataExtensions.Cast(ContentData content) +245 EPiServer.PageBase`1.get_CurrentPage() +83 ASP.views_news_articlepagetemplate_aspx.__DataBind__control9(Object sender, EventArgs e) in c:\projects\Alloy\Views\News\ArticlePageTemplate.aspx:25 System.Web.UI.Control.OnDataBinding(EventArgs e) +84 System.Web.UI.Control.DataBind(Boolean raiseOnDataBinding) +150 System.Web.UI.Control.DataBind() +17 System.Web.UI.Control.DataBindChildren() +176 System.Web.UI.Control.DataBind(Boolean raiseOnDataBinding) +160 System.Web.UI.Control.DataBind() +17 System.Web.UI.Control.DataBindChildren() +176 System.Web.UI.Control.DataBind(Boolean raiseOnDataBinding) +160 System.Web.UI.Control.DataBind() +17 System.Web.UI.Control.DataBindChildren() +176 System.Web.UI.Control.DataBind(Boolean raiseOnDataBinding) +160 System.Web.UI.Control.DataBind() +17 System.Web.UI.Control.DataBindChildren() +176 System.Web.UI.Control.DataBind(Boolean raiseOnDataBinding) +160 System.Web.UI.Control.DataBind() +17 System.Web.UI.Control.DataBindChildren() +176 System.Web.UI.Control.DataBind(Boolean raiseOnDataBinding) +160 System.Web.UI.Control.DataBind() +17 System.Web.UI.Control.DataBindChildren() +176 System.Web.UI.Control.DataBind(Boolean raiseOnDataBinding) +160 System.Web.UI.Control.DataBind() +17 Alloy.Views.News.ArticlePageTemplate.Page_Load(Object sender, EventArgs e) in C:\projects\Alloy\Views\News\ArticlePageTemplate.aspx.cs:11 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +51 System.Web.UI.Control.OnLoad(EventArgs e) +95 System.Web.UI.Control.LoadRecursive() +59 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +678 |
It turns out that router RoutePartial method requires to set two properties on segmentContext object:
- RemainingPath – the remaining URL segments that have to handled
- RoutedContentLink – current page reference
I didn’t set the RoutedContentLink property. Because of that, the template was resolved as Article, but CurrentPage was still set to NewsHome page. After I assigned selected article as RoutedContentLink the page has started to work.
1 2 3 4 5 6 7 8 9 |
public object RoutePartial(NewsHome content, SegmentContext segmentContext) { // ... segmentContext.RemainingPath = nextSegment.Remaining; segmentContext.RoutedContentLink = article.ContentLink; // ... } |
Value for URL segment
The articles page worked, but the I would like to have /news/test%20page%201/ URL to look more like this: /news/test-page-1/.
I tried to change browser URL segment to “test-page-1” but it still didn’t work.
This behavior can be changed in GetPartialVirtualPath. Instead of using “content.Name” we could use “content.UrlSegment” property. It is worth to remember that, while searching article by URL segment we cannot use name property and we should use UrlSegment property as well.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public object RoutePartial(NewsHome content, SegmentContext segmentContext) { // ... var article = this._contentRepository.GetChildren<Article>(articleContainer.ContentLink) .FirstOrDefault(a => a.URLSegment == articleName); //.FirstOrDefault(a => a.Name == articleName); // ... } public PartialRouteData GetPartialVirtualPath(Article content, string language, RouteValueDictionary routeValues, RequestContext requestContext) { return new PartialRouteData() { // .... PartialVirtualPath = string.Format("{0}/", HttpUtility.UrlPathEncode(content.URLSegment)) //HttpUtility.UrlPathEncode(content.Name)) }; } |
After this change, “test-page-1” segment starts working.
The complete snippet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public class ArticlesPartialRouter : IPartialRouter<NewHome, Article> { private readonly IContentRepository _contentRepository; public ArticlesPartialRouter(IContentRepository contentRepository) { this._contentRepository = contentRepository; } public object RoutePartial(NewsHome content, SegmentContext segmentContext) { var articleContainer = this._contentRepository.GetChildren<NewsArticlesContainer>(content.ContentLink).FirstOrDefault(); if (articleContainer == null) { return null; } var nextSegment = segmentContext.GetNextValue(segmentContext.RemainingPath); var articleName = HttpUtility.UrlDecode(nextSegment.Next); var article = this._contentRepository.GetChildren<Article>(articleContainer.ContentLink) .FirstOrDefault(a => a.URLSegment == articleName); if (article == null) { return null; } segmentContext.RemainingPath = nextSegment.Remaining; segmentContext.RoutedContentLink = article.ContentLink; return article; } public PartialRouteData GetPartialVirtualPath(Article content, string language, RouteValueDictionary routeValues, RequestContext requestContext) { return new PartialRouteData() { BasePathRoot = Configuration.Instance.NewsHomeContentLink, PartialVirtualPath = string.Format("{0}/", HttpUtility.UrlPathEncode(content.URLSegment)) }; } } |