---
title: Example of News partial routing
slug: cms-12/docs/example-of-news-partial-routing
docTags: 
createdAt: 2026-08-10T15:55:56.000Z
---

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 routes the remaining part of the URL. The partial router handles routing `Sports/TheGame/`.

## Extend routing for the NewsContent type

The following example extends routing for the `NewsContainer` content type:

```csharp
[ContentType]
public class NewsContainer: PageData {}
```

Next, implement a `NewsContent` type class:

```csharp
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. It uses Optimizely Dynamic Data Store (DDS) as external storage and no caching; it is not meant to be 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 Content Management System (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).

```csharp
[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 stored outside CMS. Use `DynamicDataStore` to store `NewsContent`. The following code creates `NewsContent` instances and stores them in `DynamicDataStore`.

```csharp
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 you create an option class `NewsPartialRouterOptions` for that and a class that configures the option `NewsPartialRouterOptionsConfigurer` as defined below:

```csharp
[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`.

```csharp
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 GetNextSegment to get the next part from the URL
    var nextSegment = urlResolverContext.GetNextSegment();

    NewsCategory category;
    if (Enum.TryParse<NewsCategory>(nextSegment.Next.ToString(), out category)) {
      nextSegment = urlResolverContext.GetNextSegment(nextSegment.Remaining);
      if (!nextSegment.Next.IsEmpty) {
        newsContent = _newsStore.RouteContent(category, HttpUtility.UrlDecode(nextSegment.Next.ToString()));
        if (newsContent != null) {
          //Update RemainingSegments so the part that we have handled is removed.
          urlResolverContext.RemainingSegments = 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 are registered in the DI container:

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

## Register MVC controllers

In 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`.

```csharp
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 news with partially routed FURLs.

```csharp
@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>
```
