Adding page-free content to your site with ASP.NET MVC

MVC ASP.NETMost implementations I have worked on have sections of the website which consist of some kind of listing, which links through to pages containing detailed content. The details pages themselves contain a single ‘Full Article’ component presentation, and are often auto-created and published using the event system. As such, the pages do not really serve any purpose other than providing a definition of the url for an article. 

This article shows an alternative approach to get rid of pages for a certain section of your site and use the features of ASP.NET MVC to show content without losing ‘real’ urls

Why are pages bad?
Firstly lets look at some of the downsides of the page based approach and how a page-free model could address these. I can think of the following as potential issues:

  1. The event system is required to auto create our pages. This of course costs development time, and puts some load on our CMS. The alternative is to have the editor manually create the pages, but this reduces their productivity, and there is no real benefit.
  2. We need to implement logic to organize our pages. If we don’t we will end up with lots of pages in the same Structure Group, which can give problems both on the CMS and the web application.
  3. If we want to archive or delete the content, either on the CMS or site (or both). We will need to find and move/un/republish/delete the pages as well – again this is unnecessary effort (automated or manual). If the page is auto-created, the editor may even not understand where to find it.

Now lets say we use a page free model. An editor creates a News component and publishes it in the appropriate publications – That’s simple, both for the editor and any events implementation (publishing can still be automated if required). Lets assume that there is a metadata field Url on the component which helps defines the url of the item – we don’t have pages but we still want to be able to use a real URL like news/{Url}.html (rather than news.aspx?id=1234) for SEO purposes. As the news component is a component presentation (most likely sitting in your broker database) we don’t have to worry about cluttering up a particular folder on the webserver, or structure group in the CMS. If we want to delete the article, we simply un-publish and delete. If we want to archive it, maybe we don’t have to do anything at all (I will come to this later).

How to implement?
So how difficult is this to implement? MVC for ASP.NET was designed for just this kind of page-free approach, so this can be achieved quite simply. Heres how…

1. Route your URLS
Routing is not something specific to MVC, but MVC makes use of it to determine which controller to use to process a request. In your Global.asax file add something like the following in Application_Start():

RouteTable.Routes.MapRoute("News", "news/{*url}", new {controller="DynamicContent", action="Render", contentType="News"});

What this means is that when a request comes in for anything in the news section, it will be routed to the Render method of the DynamicContentController class.

2. Create your controller
Create a class called DynamicContentController which inherits from System.Web.Mvc.Controller. Add a method called Render as follows:

Note the parameters to the Render() method. The names of these match the segments in your route pattern (the url from news/{*url}) and any additional parameters you defined in the call to routes.MapRoute (contentType).

The GetView method defines what view to use to render the content (we will look at this later). The TridionModel.GetContent method is where we are going use the Tridion CD API to load content, which is the next step.

3. Create your model to load content
Before we do start, we need to think about how to get the content based on the url. One way is to do a query on the Url metadata field, however this is risky – we may have two items which happen to have the same metadata field value. For this example we will assume that our url to view a news article is in the form news/{Url}_{ItemId}.html – this way we combine the SEO benefits of a real url, plus the precision of using ids to define which content to show. We create a TridionModel class with GetContent method like this:

It is worth noting that we are working with pure XML for simplicity and flexibility -I assume we publish news in XML format using a dynamic component template with output format Xml Document. The View function in the controller takes any object, so you can use whatever format and data object you want (HTML string, or indeed the ComponentPresentation object itself). This data is what is passed to the view for rendering, which is the next step…

4. Create your view
ASP.NET MVC has various view engines, the defaults being Razor and ASPX. For this example I am going to use ASPX views (there is a good reason for this in the context of what we are doing – see later!). Create a folder called Views in the root of the website, with a DynamicContent subfolder, and within this NewsDetail.aspx (the filename needs to match the name set by our GetView method).

Note here the Inherits element at the top – this defines that the view is passed an XDocument object as the model (this is what we return from the GetContent method).The XML I am using for my articles is something like the following:

Now you are ready to test it out. Compile the web app, ensure a news component is published and navigation to a page (eg /news/company-results-2011_3482.html).

5. Listings
Creating a list is also pretty easy. Lets assume that we want the list to appear when we just enter the url /news. We update our GetView method to return a different view depending on the url:

private string GetView(string contentType, string url)
    return "News" + TridionModel.GetViewType(url);

We add this GetViewType method to ViewHelper:

public static string GetViewType(string url)
    return url.Contains(".html") ? "Detail" : "List";

We update the GetContent method to also handle broker queries:

public XDocument GetContent(string contentType, string url)
    int publicationId = Int32.Parse(ConfigurationManager.AppSettings["tridion:publicationid"]);
    var cpf = new ComponentPresentationFactory(publicationId);
    if (GetViewType(url) == "Detail")
        int itemId = GetIdFromUrl(url);
        if (itemId > 0)
            var cp = cpf.GetComponentPresentationWithOutputFormat(itemId, "Xml Document");
            if (cp != null)
                return XDocument.Parse(cp.Content);
        Query query = BuildQuery(contentType, url, publicationId);
        XDocument result = XDocument.Parse("<items/>");
        string[] compUris = query.ExecuteQuery();
        foreach(string uri in compUris)
            var cp = cpf.GetComponentPresentationWithOutputFormat(uri, "Xml Document");
            if (cp != null)
    return null;

private Query BuildQuery(string contentType, string url, int pubId)
    List<Criteria> criteria = new List<Criteria>();
    criteria.Add(new SchemaTitleCriteria(contentType));
    criteria.Add(new PublicationCriteria(pubId));
    Query result = new Query(new AndCriteria(criteria.ToArray()));
    result.AddSorting(new SortParameter(SortParameter.ItemInitialPublicationDate, SortParameter.Descending));
    return result;

Add methods to ViewHelper to to resolve links

public static string GetItemLink(XElement item, string routeName)
    string id = item.Attribute("id").Value;
    string urlMeta = item.Element("metadata").Element("url").Value;
    return GetItemLink(Int32.Parse(id), urlMeta, routeName);

public static string GetItemLink(int itemid, string itemUrlMeta, string routeName)
    var path = RouteTable.Routes.GetVirtualPath(null, routeName, new RouteValueDictionary { { "url", String.Format("{0}_{1}.html", itemUrlMeta, itemid) } });
    return path.VirtualPath;

And finally we create a NewsList.aspx View

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<XDocument>" %>
        <title>News List</title>
        <meta name="description" content="All the latest news" />
        <h1>News List</h1>
        <% foreach(var item in Model.Descendants("item")) {%>
            <h2><a href="<%=Example.MvcDemo.ViewHelper.GetItemLink(item,"News")%>"><%=item.Element("heading").Value %></a></h2>
            <p><%=item.Element("metadata").Element("description") %></p>
        <%} %>      

6. Archiving
This almost comes for free, suppose we want to only show news for the current year in the list, but have older news available with urls like /news/archive/2011/. All we have to do is alter the logic in the BuildQuery method to put some date criteria based on the url:

7. More! more! more!
And its very easy to add even more functionality, for example:

  • Paging the lists (read query string paramters like start and page-size from the url and add a PagingFilter to your query)
  • Filtering using taxonomies/keywords (map url segments to keywords, for example news/sport/)
  • Adding a whole new dynamic section, for example Products or Events, by simply adding a new route mapping with a different contentType parameter

However, the most powerful aspect of using an MVC approach is to enable a publish-once, deliver-multiple solution for providing your content in different formats and channels. Now that you have set your content free from the pages, you can easily add some rules to the Controller GetView method, to enable url-based switching between different views and thus different output formats:

  • Add RSS feeds by processing urls prefixed with RSS (rss/news/) using the same listing model content but instead of a ASPX view, use XSLT (you can implement a custom View Engine which uses XSLT see here for an example)
  • Add a JSON based Latest news widget to my homepage, by creating a simple JSON view engine (there are lots of XML to JSON libraries out there)
  • Present the raw XML and syndicate my content to other applications and websites
  • Use a HTML to PDF library to generate PDFs using a PDF View Engine.

None of this is new of course, but ASP.NET MVC makes it a lot easier to configure and implement, reuse your model and create reusable view engines for different output formats.

I have created a Visual Studio project with all the above code, plus some extra goodies like Raw XML/XSLT/JSON View Engines, which you can download here. There is also a demo model which loads content from an example dcp xml file, so you can play with it offline from Tridion CD. You will need Visual Studio 2010+ with ASP.NET MVC 3+ to run it and be sure to read the README file first…

Master Pages

To return to an earlier point as promised: Why did I use ASPX instead of the (more popular) Razor view engine? the answer is that Razor is great if you are starting a site from scratch, however the scenario I am considering here is to integrate MVC into an existing, classic ASP.NET website. The rest of our site is served by aspx pages, using a master page, and I want to reuse that master page for my MVC pages. This is easiest if we use the ASPX view engine, as the Razor engine has a different concept – Layouts. If you are interested to read more there are some workarounds for this, discussed here


One issue has escaped our attention so far; linking. With a page based approach, you know that links to your news components from the rest of your site the content will resolve to urls. If we don’t have pages, they will not. So what do we do?

There are a number of possible approaches:

Firstly we could resolve the links at publish time with a custom link resolver TBB which runs before the default Link Resolver TBB. This would check the schema and if it was News, write out a url in the format /news/{urlmeta}_{itemid}.html. This is not really dynamic however (if the item is unpublished the now-broken link still appears)

What would make more sense is to write out a custom format for news links:

<dynamic:Link runat="server" url="{urlmeta} itemid="{id}" 
   publicationid="{pubid}" textonfail="true">link text</dynamic:Link>

You can then implement this control, which uses the broker API to check if the component is published, before rendering the link url.

Another option is to extend the ComponentLink control itself (see my article here on how to do this) to have a schema check first, and if it is news do the logic just mentioned, and if not, do normal dynamic linking. The advantage of this is you dont need to know anything about the front-end behaviour of your system at publish time.

Lastly, don’t forget that if you have rich text fields in your news components it is always possible to these could contain links, so you will need to add some dynamic link processing in your views.

More MVC

This article gives a gentle introduction to MVC for ASP.NET and Tridion, and a way to integrate it on an existing site. An article about Tridion and MVC would not be complete without mentioning the excellent DD4T open source library. If you are seriously interested in an MVC approach to architecting your whole website, then I recommend checking this out. Its much more sophisticated than what I have outlined above, enabling you to also serve your page-based content from MVC. It also has standard templates for publishing the XML consumed for the model, and lots of examples to help you understand how to use it.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>