This content is archived. See latest version here.

Last updated: Mar 31 2014

Table of contents

Introduction

This example will implement partial routing for URLs like http://site/News/Sports/TheGame/. In the example the part of the url that is http://site/News/ is the url to a page instance of model type NewsContainer. Then by registering a partial router for ModelType NewsContainer the partial router takes care of routing the remaining part of the url. In the example the partial router will take care of routing the part 'Sports/TheGame/'.

The NewsContent type

We want to extend routing for content type NewsContainer which look like.

C#
[ContentType]
public class NewsContainer : PageData
{ }

Then we implement a type NewsContent. The class looks like:

C#
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; }
}

Implementing NewsContentStore

This is an example where NewsContent is stored outside EPiServer CMS (in Dynamic Data Store) therefore we will write a simple store that deliver NewsContent instances. Another option would be to return the routed data as IContent instances. In that case the data could either be delivered through a custom content provider (see section Content Providers) or the data can be stored in EPiServer CMS. Then no provider would be needed instead the data could be loaded/saved through EPiServer.IContentRepository (see section Persisting IContent instances).

In this example we want to partial route URL parts like 'Sports/TheGame/' so we add a method to our store that accepts a category (corrsponds to 'Sports' in URL example) and the name of an article (corresponds to 'TheGame' in URL example). The store implementation look like:

C#
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();
    }
}

Implementing IPartialRouter

We want the partial router to be able to handle incoming request beyond pages of type NewsContainer. We also want to be able to create outgoing FURLs for instances of NewsContent. The implementation of the partial router looks like:

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

    public NewsPartialRouter(NewsContentStore newsStore, ContentReference newsContainer)
    {
        _newsStore = newsStore;
        _newsContainer = newsContainer;
    }

    #region RoutePartial
    public object RoutePartial(NewsContainer content, SegmentContext segmentContext)
    {
        //The format we handle is category/Name/
        NewsContent newsContent = null;

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

        NewsCategory category;
        if (Enum.TryParse<NewsCategory>(nextSegment.Next, out category))
        {
            nextSegment = segmentContext.GetNextValue(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.
                    segmentContext.RemainingPath = nextSegment.Remaining;
                }
            }
        }

        return newsContent;
    }
    #endregion

    #region GetPartialVirtualPath
    public PartialRouteData GetPartialVirtualPath(NewsContent content, string language, RouteValueDictionary routeValues, RequestContext requestContext)
    {
        if (ContentReference.IsNullOrEmpty(_newsContainer))
        {
            throw new InvalidOperationException("property NewsContainer must be set on start page");
        }
        return new PartialRouteData()
        {
            BasePathRoot = _newsContainer,
            PartialVirtualPath = String.Format("{0}/{1}/",
                content.Category.ToString(),
                HttpUtility.UrlPathEncode(content.Name))
        };
    }
    #endregion

}

Registering a router

Below is the code initialization code that registers the partial router. Note that the code requires using statements for the namespaces EPiServer and EPiServer.Web.Routing

C#
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class InitializationModule : IInitializableModule
{
    public void Initialize(EPiServer.Framework.Initialization.InitializationEngine context)
    {
        //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 = context.Locate.ContentLoader().GetChildren<NewsContainer>(ContentReference.StartPage);
        if (newsPage == null)
        {
             newsPage = context.Locate.ContentRepository().GetDefault<NewsContainer>(ContentReference.StartPage);
             newsPage.Name = "News";
             context.Locate.ContentRepository().Save(newsPage, SaveAction.Publish, AccessLevel.NoAccess);
}
var partialRouter = new NewsPartialRouter(new NewsContentStore(), newsPage.ContentLink.ToReferenceWithoutVersion()); RouteTable.Routes.RegisterPartialRouter<NewsContainer, NewsContent>(partialRouter); NewsContentGenerator.CreateFakeData(); } public void Preload(string[] parameters) { } public void Uninitialize(EPiServer.Framework.Initialization.InitializationEngine context) { } }

Registering MVC controllers

In EPiServer CMS you register a MVC controller or WebForm for a Model type by implementing interface EPiServer.Web.IRenderTemplate<TModel>. This is done implicitly if your controller inherits EPiServer.Web.Mvc.PageController<TPage> or EPiServer.Web.Mvc.BlockController<TBlock> or if your WebForm inherits EPiServer.PageBase<TPage>. Below is the code for a Mvc controller for NewsContent and NewsContainer. It uses an extension method in namespace EPiServer.Web.Routing.

C#
public class NewsContentController : System.Web.Mvc.Controller, IRenderTemplate<NewsContent>
{
    public ActionResult Index()
    {
        //You get the routed custom data from extension method in EPiServer.Web.Routing
        var newsContent = Request.RequestContext.GetRoutedData<NewsContent>();
        return View(newsContent);
    }
}
C#
public class NewsContainerController : PageController<NewsContainer>
{
    public ActionResult Index()
    {
        //The view for news container displays a list of all news.
        var newsStore = new NewsContentStore();
        return View(newsStore.News.ToList());
    }
}

Creating MVC views

To display a single news from the above controller we create a view located as /Views/NewsContent/index.aspx. We also create a view for the NewsContainer class that lists all news with partial routed FURLs. The below code is the code for the views.

C#
<%@ Page Title="" Language="C#"
Inherits="System.Web.Mvc.ViewPage<CodeSamples.Additional_Content.HowTo.PartialRouting.NewsContent>" %>

<h2><%=Model.Name%></h2>
<p><%=Model.Body %></p>
C#
<%@ Page Title="" Language="C#" 
Inherits="System.Web.Mvc.ViewPage<List<CodeSamples.Additional_Content.HowTo.PartialRouting.NewsContent>>" %>
<%@ Import Namespace="System.Web.Routing" %>
<%@ Import Namespace="EPiServer.Web.Routing" %>
<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>

Creating outgoing URLs

To create an outgoing url for a NewsContent item you can call method EPiServer.Web.UrlResolver.Current.GetVirtualPathForNonContent. For IContent instances it is also possible to call methods GetVirtualPath or GetUrl and pass in the reference to the content. In web forms it is also possible to create a link in the html like '/Templates/NewsContent.aspx?id=7__news' then the FURL module will call the partial router during outgoing parsing to create an FURL for the content. Below is an example of how outgoing FURLs can be constructed.

C#
NewsContent news = GetNewsContent();
var furl = urlResolver.GetVirtualPathForNonContent(news, null, null).GetUrl();

//In case the item is an IContent then in webforms you can also output a link like:
IContent iContent = GetIContent();
var anchor = new System.Web.UI.HtmlControls.HtmlAnchor();
anchor.HRef = "/templates/NewsContent.aspx?id=" + iContent.ContentLink.ToString();

Creating NewsContent

In this example we show how data stored outside EPiServer CMS can be routed to. In this example we use DynamicDataStore to store our NewsContent. The follow code creates some NewsContent instance and store them in DynamicDataStore.

C#
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 = NewsCateory.Sports,
                Name = "Sweden",
                Body = "Sweden have qualified for EURO 2012 championship in soccer"
            };
            newsContentStore.Save(soccerNews);

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

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

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

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

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

See also

  • In the Relate template package you can see an implementation of partial routing for blogs and blog entries.
  • Refer to the Content Providers section in the EPiServer CMS SDK for more information on how to create content providers.
  • In the SDK framework reference you will find more information on the EPiServer.Web.Routing.IPartialRouter<TIncoming, TOutgoing> and base class.

Comments