Hide menu Last updated: Sep 21 2015

Extend routing for the NewsContent type

The following example extends routing for the NewsContainer content type:

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

Next, implement a NewsContent type class:

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

Implement NewsContentStore

Create a simple store to deliver NewsContent instances because NewsContent is stored outside EPiServer 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 (as described in section Content Providers). Or, you can store the data in EPiServer CMS so that no provider is needed and the data can be loaded or saved through EPiServer.IContentRepository (as described in section Persisting IContent instances).

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).

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

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.

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

}

Register a router

The following example initializes code that registers the partial router. The code requires using statements for the EPiServer and EPiServer.Web.Routing namespaces . 

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).FirstOrDefault();
        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) { } }

Register MVC controllers

In Episerver CMS, you register a MVC controller or WebForm 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>, or if your WebForm inherits EPiServer.PageBase<TPage>. The following code is for a MVC controller for NewsContent and NewsContainer that uses an extension method in the EPiServer.Web.Routing  namespace.

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

Create MVC views

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

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>

Create outgoing URLs

To create an outgoing URL for a NewsContent item, call the EPiServer.Web.UrlResolver.Current.GetVirtualPathForNonContent method. For IContent instances, you also can call the GetVirtualPath or GetUrl methods and pass in the reference to the content. In web forms, you can create a link in the HTML, such as /Templates/NewsContent.aspx?id=7__news, then the FURL module calls the partial router during outgoing parsing to create an FURL for the content. The following example shows how to construct outgoing FURLs.

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();

Create NewsContent

The following example shows how to route to data that is stored outside EPiServer CMS. Use DynamicDataStore to store NewsContent. The following code creates NewsContent instances and stores 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 = NewsCategory.Sports,
                Name = "Sweden",
                Body = "Sweden have qualified for EURO 2012 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);
        }
    }
}

Related topics

Comments

In the Register a Router section, the sample code contains the following:

        var newsPage = context.Locate.ContentLoader().GetChildren<NewsContainer>(ContentReference.StartPage);
        if (newsPage == null)
        {
             newsPage = context.Locate.ContentRepository().GetDefault<NewsContainer>(ContentReference.StartPage);

What var newsPage is created, it's being assigned a value from GetChildren, which returns and sets newsPage to a type of IEnumerable<NewsContainer>. When an attempt to reassign is made using GetDefault<NewsContainer>, which returns a type of NewsContainer. Attempting to assign a type of NewsContainer to newsPage, which is of type IEnumerable<NewsContainer> results in an error. There is a possible cast, but that's not used here.

In my code, I've changed it to add "FirstOrDefault" to the first line. E.g.:

var newsPage = context.Locate.ContentLoader().GetChildren<NewsContainer>(ContentReference.StartPage).FirstOrDefault();

Which seems to result in the right types being assigned as well as serve the purpose of discovering whether a page of that type exists.

Thank you for the feedback. The code sample has now been updated.