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

Loading...
Area: Optimizely CMS
Applies to versions: 12 and higher
Other versions:
ARCHIVED This content is retired and no longer maintained. See the version selector for other versions of this topic.

Example of News partial routing

Recommended reading 
Note: This documentation is for the preview version of the upcoming release of CMS 12/Commerce 14/Search & Navigation 14. Features included here might not be complete, and might be changed before becoming available in the public release. This documentation is provided for evaluation purposes only.

This topic shows how to implement partial routing in Optimizely CMS. In the example, the part of the URL that is http://site/News/ is the URL to a page instance of model type NewsContainer. By registering a partial router for type NewsContainer, the partial router takes care of routing the remaining part of the URL. The partial router handles routing Sports/TheGame/.

In this topic

Extend routing for the NewsContent type

The following example extends routing for the NewsContainer content type:

[ContentType]
public class NewsContainer : PageData
{ }

Next, implement a NewsContent type class:

public enum NewsCategory
{
  Sports,
  News, 
  Economy
}
public class NewsContent
{
  public virtual NewsCategory Category { get; set; }
  public virtual string Name { get; set; }
  public virtual string Body { get; set; }
}

This code sample is just one way of implementing a partial router and uses DDS as external storage and no caching, it is not meant as production-ready code.

Implement NewsContentStore

Create a simple store to deliver NewsContent instances because NewsContent is stored outside Optimizely CMS (in Dynamic Data Store).

You also could return the routed data as IContent instances, where the data can be delivered through a custom content provider. Or, you can store the data in Optimizely CMS so that no provider is needed and the data can be loaded or saved through EPiServer.IContentRepository.

In the following example, partially route URL parts like Sports/TheGame/ by adding a method to the store that accepts a category (that corresponds to Sports in the URL example) and the name of an article (that corresponds to TheGame in the URL example).

[ServiceConfiguration]
public class NewsContentStore
{
  //In this example the data is stored in DDS
  public IOrderedQueryable<NewsContent> News 
  { 
    get 
    { 
      return DynamicDataStoreFactory.Instance.
      GetStore(typeof(NewsContent)).Items<NewsContent>(); 
    } 
  }

  public NewsContent RouteContent(NewsCategory category, string name)
  {
    return News.Where(n => n.Category == category
      && n.Name == name)
      .SingleOrDefault();
  }
}

Create NewsContent

The following example shows how to route to data that is stored outside Optimizely CMS. Use DynamicDataStore to store NewsContent. The following code creates NewsContent instances and stores them in DynamicDataStore.

public class NewsContentGenerator
{
  public static void CreateFakeData()
  {
    var newsContentStore = DynamicDataStoreFactory.Instance.GetStore(typeof(NewsContent));
    if (newsContentStore == null)
    {
      newsContentStore = DynamicDataStoreFactory.Instance.CreateStore(typeof(NewsContent));

      var soccerNews = new NewsContent()
      {
        Category = NewsCategory.Sports,
        Name = "Sweden",
        Body = "Sweden have qualified for EURO championship in soccer"
      };
      newsContentStore.Save(soccerNews);

      var olymicNews = new NewsContent()
      {
        Category = NewsCategory.Sports,
        Name = "Olympic",
        Body = "The next summer olympic will take place in London"
      };
      newsContentStore.Save(olymicNews);

      var euroNews = new NewsContent()
      {
        Category = NewsCategory.Economy,
        Name = "Euro",
        Body = "The euro has reached new levels"
      };
      newsContentStore.Save(euroNews);

      var oilNews = new NewsContent()
      {
        Category = NewsCategory.Economy,
        Name = "Oil",
        Body = "New oil findings have been made in the artic"
      };
      newsContentStore.Save(oilNews);

      var politicNews = new NewsContent()
      {
        Category = NewsCategory.News,
        Name = "Selection",
        Body = "Sweden have selected a new government"
      };
      newsContentStore.Save(politicNews);

      var earthQuakeNews = new NewsContent()
      {
        Category = NewsCategory.News,
        Name = "Eartquake",
        Body = "An earthquake was registered this morning"
      };
      newsContentStore.Save(earthQuakeNews);
    }
  }
}

Create options

The partial router will need a news container in CMS that defines the root in CMS from where the partial routing should start. Here we create an option class NewsPartialRouterOptions for that and a class that configures the option NewsPartialRouterOptionsConfigurer as defined below:

[Options]
public class NewsPartialRouterOptions
{
  public ContentReference NewsContainer { get; set; }
}

public class NewsPartialRouterOptionsConfigurer : IConfigureOptions<NewsPartialRouterOptions>
{
  private readonly IContentRepository _contentRepository;

  public NewsPartialRouterOptionsConfigurer(IContentRepository contentRepository)
  {
    _contentRepository = contentRepository;
  }

  public void Configure(NewsPartialRouterOptions options)
  {
    //It checks for a page of type NewsContainer named News under start page. If it does not exist it gets created.
    //Then partial routing is extended beyond that page.
    var newsPage = _contentRepository.GetChildren<NewsContainer>(ContentReference.StartPage).FirstOrDefault();

    if (newsPage == null)
    {
      newsPage = _contentRepository.GetDefault<NewsContainer>(ContentReference.StartPage);
      newsPage.Name = "News";
      _contentRepository.Save(newsPage, SaveAction.Publish, AccessLevel.NoAccess);
    }

    options.NewsContainer = newsPage.ContentLink.ToReferenceWithoutVersion();
    NewsContentGenerator.CreateFakeData();
  }
}

Implement IPartialRouter

Make the partial router handle incoming requests beyond pages of type NewsContainer. Also, allow outgoing FURLs to be created for instances of NewsContent.

public class NewsPartialRouter : IPartialRouter<NewsContainer, NewsContent>
{
  private readonly NewsContentStore _newsStore;
  private readonly ContentReference _newsContainer;

  public NewsPartialRouter(NewsContentStore newsStore, NewsPartialRouterOptions options)
  {
    _newsStore = newsStore;
    _newsContainer = options.NewsContainer;
  }

  public object RoutePartial(NewsContainer content, UrlResolverContext urlResolverContext)
  {
    //The format we handle is category/Name/
    NewsContent newsContent = null;

    //Use helper method GetNextRemainingSegment to get the next part from the URL
    var nextSegment = urlResolverContext.GetNextRemainingSegment(urlResolverContext.RemainingPath);

    NewsCategory category;
    if (Enum.TryParse<NewsCategory>(nextSegment.Next, out category))
    {
      nextSegment = urlResolverContext.GetNextRemainingSegment(nextSegment.Remaining);
      if (!String.IsNullOrEmpty(nextSegment.Next))
      {
        newsContent = _newsStore.RouteContent(category, HttpUtility.UrlDecode(nextSegment.Next));
        if (newsContent != null)
        {
          //Update RemainingPath so the part that we have handled is removed.
          urlResolverContext.RemainingPath = nextSegment.Remaining;
        }
      }
    }

    return newsContent;
  }

  public PartialRouteData GetPartialVirtualPath(NewsContent content, UrlGeneratorContext urlGeneratorContext)
  {
    return new PartialRouteData()
    {
      BasePathRoot = _newsContainer,
      PartialVirtualPath = String.Format("{0}/{1}/",
      content.Category.ToString(),
      HttpUtility.UrlPathEncode(content.Name))
    };
  }
}

Register a router

The following example shows how the partial router and the options configurer is registered in DI container: 

//here services is IServiceCollection
services.AddSingleton<IPartialRouter, NewsPartialRouter>();
services.AddTransient<IConfigureOptions<NewsPartialRouterOptions>, NewsPartialRouterOptionsConfigurer>();

Register MVC controllers

In Optimizely CMS, you register an MVC controller for a Model type by implementing the EPiServer.Web.IRenderTemplate<TModel> interface. This is done implicitly if your controller inherits EPiServer.Web.Mvc.PageController<TPage> or EPiServer.Web.Mvc.BlockController<TBlock>. The following code is for an MVC controller for NewsContent and NewsContainer.

public class NewsContentController : Controller, IRenderTemplate<NewsContent>
{
  public ActionResult Index()
  {
    //You get the routed custom data from IContentRouteFeature
    var newsContent = HttpContext.Features.Get<IContentRouteFeature>().RoutedContentData.PartialRoutedObject as NewsContent;
    return View(newsContent);
  }
}
public class NewsContainerController : PageController<NewsContainer>
{
  private readonly NewsContentStore _newsContentStore;

  public NewsContainerController(NewsContentStore newsContentStore)
  {
    _newsContentStore = newsContentStore;
  }
  public ActionResult Index()
    {
      //The view for news container displays a list of all news.
      return View(_newsContentStore.News.ToList());
    }
}

Create MVC views

To display a single news from the above controller, create a view located as /Views/NewsContent/index.cshtml. Also, create a view for the NewsContainer class that lists all news with partial routed FURLs. 

@model NewsContent
@{ Layout = null; }
<h2>@Model.Name</h2>
<p>@Model.Body</p>
@model IList<NewsContent>
@{ Layout = null; }
<h2>List of news</h2>
<ul>
  @foreach (var news in Model)
  {
    <li>
      <a href="@UrlResolver.Current.GetVirtualPathForNonContent(news, null, null).GetUrl()">@news.Name</a>
    </li>
  }
</ul>

Related topics

Do you find this information helpful? Please log in to provide feedback.

Last updated: Jul 02, 2021

Recommended reading