Getting the current content item in Orchard

By "current", I mean the content item being routed to via the url. For example, if the url is http://mysite.com/about- us, a content item with the display alias "about-us" will be rendered.

In this post we will see how we can find out what content item is being rendered based on the current url.

 

Routing

The key to getting the ID of the current content item being presented is to be able to map the request url to a content item ID.

To understand how we can achieve this, it is important to understand how Orchard is able to map a url to a content item, so let's quickly revisit that.

In MVC, the Routing mechanism is responsible for mapping incoming requests to controllers.

In Orchard, this is no different. Orchard exposes a default route that looks like "{area}/{controller}/{action}/{id}".

Whenever Orchard asks a content item what it's display url is (using ContentManager.GetItemMetadata), the default content handler will return "Item" as the controller, "Display" as the action and the content id as the id (See Orchard.Core/Contents/Handlers/ContentsHandler.cs).

That means that whenever content is being displayed using the default route, we should be able to access the current content ID by getting the value of RequestContext.RouteData.Values["id"].

 

Autoroute & Alias

But wait a minute, how does that work when a content type has the AutoroutePart attached? You see, Autoroute comes with its own routing mechanism. It enables us to provide highly customized routing patterns. So the question is, whenever a request comes in to display a content item that has the AutoroutePart attached, will we still have access to the value of RequestContext.RouteData.Values["id"]? Because the URL will no longer look like this: "Contents/Item/Display/42". Instead, it will look like this: "/about-us".

Digging into the Autoroute and Alias source code I found some great news: Alias implements a custom route class called AliasRoute, and what it essentially does is map the incoming request path (e.g. "/about-us") to the stored display route values (e.g. "Contents/Item/Display/42"), and sets the RequestContext.RouteData.Values to these values. Awesome!

 

Introducing CurrentContentAccessor

So now we figured all that out, what will the code look like to actually load the current item?

What about this:

private int? GetCurrentContentItemId() {
 object id;
 if (_requestContext.RouteData.Values.TryGetValue("id"out id)) {
  int contentId;
  if(int.TryParse(id as stringout contentId))
   return contentId;
 }
 return null;
}

 

If a value with key "id" exists, we try and convert it into an Int32 and return it. If we can't find an "id" value or can't convert it into an Int32, we return null.

An example of where null will be returned is in scenarios where a url is being routed to a custom controller action. Such an action may have nothing to do with content rendering, so it's important to realize that this function is only useful for content items being rendered.

 

Use Cases

So where and when might getting the current content be useful?

One scenario could be displaying related articles in a widget. For example, consider you have a blog post with a taxonomy field attached called "Tags". On the details page for this post, you want to render a widget that lists all posts that have the same tags associated with.

In order to implement such a widget, it would be useful to know which content item is being requested so the widget can find out which tags are associated.

Another scenario could be displaying recommended products in an e-commerce app, where the current content item being displayed is a Product, and the RecommendationsWidget is written to get the current content item, checks if its a Product, and renders a list of recommendations.

 

Although implementing that sort of widgets is outside the scope of this article, we will implement a reusable class called CurrentContentAccessor and demonstrate it by implementing a simple widget called CurrentContentWidget.

For this demo I created a simple module called Skywalker.Utils.

 

CurrentContentAccessor

 

Services/CurrentContentAccessor.cs:

using System.Web.Routing;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Core.Common.Utilities;
 
namespace Skywalker.Utils.Services {
 public interface ICurrentContentAccessor : IDependency {
  ContentItem CurrentContentItem { get; }
 }
 
 public class CurrentContentAccessor : ICurrentContentAccessor {
  private readonly LazyField<ContentItem> _currentContentItemField = new LazyField<ContentItem>();
  private readonly IContentManager _contentManager;
  private readonly RequestContext _requestContext;
 
  public CurrentContentAccessor(IContentManager contentManagerRequestContext requestContext) {
   _contentManager = contentManager;
   _requestContext = requestContext;
   _currentContentItemField.Loader(GetCurrentContentItem);
  }
 
  public ContentItem CurrentContentItem {
   get { return _currentContentItemField.Value; }
  }
 
  private ContentItem GetCurrentContentItem() {
   var contentId = GetCurrentContentItemId();
   return contentId == null ? null : _contentManager.Get

(contentId.Value);
  }
 
  private int? GetCurrentContentItemId() {
   object id;
   if (_requestContext.RouteData.Values.TryGetValue("id"out id)) {
    int contentId;
    if(int.TryParse(id as stringout contentId))
     return contentId;
   }
   return null;
  }
 }
}

 

That's a lot more code than the initial method we introduced, but the gist of the class is the GetCurrentContentItemId method.

Because it requires an instance of RequestContext, we inject that one via the constructor.

We also created a private wrapper method called GetCurrentContentItem, which requires an instance of IContentManager, so we injected that one as well.

For the users of this class, we implemented an easy to use public property called CurrentContentItem. Because we don't want to load the same content item over and over multiple times whenever this property is invoked, we implemented it as a LazyField, which we setup in the constructor of this class. That way, client code can request the current content item as many times as they want to, but only one call to the content manager will be made (for the life of the current HTTP request of course).

 

Let's see how we can use this class by implementing a custom widget.

Migrations

Migrations.cs:

using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;
 
namespace Skywalker.Utils {
 public class Migrations : DataMigrationImpl {
   public int Create

() {
 
    ContentDefinitionManager.AlterPartDefinition("CurrentContentPart"part => part
     .Attachable()
     .WithSetting("Description""Makes the current content item being rendered by the current url availble"));
 
    ContentDefinitionManager.AlterTypeDefinition("CurrentContentWidget"type => type
     .WithPart("CommonPart")
     .WithPart("WidgetPart")
     .WithPart("CurrentContentPart")
     .Creatable(false)
     .Draftable(false)
     .WithSetting("Stereotype""Widget"));
 
    return 1;
   }
 }
}

 

 

We're defining a part called CurrentContentPart and a type called CurrentContentWidget with a couple of parts and configured to be a Widget.

Nothing new here, except for the call to .WithSetting which sets a content part description. This will be a new feature of Orchard 1.7.

 

Part

Models/CurrentContentPart.cs:

using Orchard.ContentManagement;
using Orchard.Core.Common.Utilities;
 
namespace Skywalker.Utils.Models {
 public class CurrentContentPart : ContentPart {
  internal LazyField<ContentItem> CurrentContentItemField = new LazyField<ContentItem>();
  
  public ContentItem CurrentContentItem {
   get { return CurrentContentItemField.Value; }
  }
 }
}

 

A very simple content part class with just one public property: CurrentContentItem. This will make it easy for users of this part to quickly and efficiently access the current content item.

We implemented this member as another lazy field. The reason for that is because in order to use our CurrentContentAccessor class, it needs to be injected, and as you may know that is not possible with content parts (nor would that be a good I idea I suppose).

We will setup this lazy field from the content handler.

 

Driver

Drivers/CurrentContentPartDriver.cs:

using Orchard.ContentManagement.Drivers;
using Skywalker.Utils.Models;
 
namespace Skywalker.Utils.Drivers {
 public class CurrentContentPartDriver : ContentPartDriver<CurrentContentPart> {
  protected override DriverResult Display(CurrentContentPart partstring displayTypedynamic shapeHelper) {
   return ContentShape("Parts_CurrentContentItem", () => shapeHelper.Parts_CurrentContentItem());
  }
 }
}

 

This driver does nothing except for yielding a shape called Parts_CurrentContentItem. This enables themers to customize how this shape will look like. Or, themers can choose to not render this shape using Placement.info and completely customize how a content item / widget looks like. Thanks to the CurrentContentItemPart, they will be able to render stuff based on the current content item.

 

Handler

Handlers/CurrentContentPartHandler.cs:

using Orchard.ContentManagement.Handlers;
using Skywalker.Utils.Models;
using Skywalker.Utils.Services;
 
namespace Skywalker.Utils.Handlers {
 public class CurrentContentPartHandler : ContentHandler {
  private readonly ICurrentContentAccessor _currentContentAccessor;
 
  public CurrentContentPartHandler(ICurrentContentAccessor currentContentAccessor) {
   _currentContentAccessor = currentContentAccessor;
   OnActivated<CurrentContentPart>(SetupLazyFields);
  }
 
  private void SetupLazyFields(ActivatedContentContext contextCurrentContentPart part) {
   part.CurrentContentItemField.Loader(() => _currentContentAccessor.CurrentContentItem);
  }
 }
}

 

The handler too is very simple. All it does is setup the CurrentContentItemField of the CurrentContentPart when content that uses this part is activated.

Note that this is the only place where we are actually using the CurrentContentAccessor class, but you can use it from any class you like, be it a handler, controller, menu provider, shape table provider, custom classes, etc.

 

View

Views/Parts/CurrentContentItem.cshtml:

@using Orchard.ContentManagement
<h3>Current Page</h3>
@Html.ItemDisplayLink((ContentItem)Model.ContentPart.CurrentContentItem)

 

All we do out of the box is render a link to the current content item. Probably not very useful in real world websites, but perfect for demo purposes.

 

Placement

Finally, we need to configure where our shape will be rendered.

Placement.info:

<Placement>
 <Match DisplayType="Detail">
 <Place Parts_CurrentContentItem="Content:0" />
 </Match>
</Placement>

 

Notice that we are explicitly placing the shape only when the displaytype of the content to which the CurrentContentPart is attached to is rendered is "Detail". This will prevent our shape from being rendered in Summary and AdminSummary display types.

Themes can override this configuration in anyway that makes sense for that theme.

That's it! Let's see if it works.

 

Enabling the Module

 

Creating a Test Content Type

We already have the Page content type of which we know has the AutouroutePart attached. Let's also create a content type that doesn't have the AutoroutePart attached, just to see that our CurrentContentAccessor works as expected.

Go to Content -> Content Types and hit the Create Content Type button. Call the type anything you like (I'm calling it RawPage because it feels that way without having an AutoroutePart attached) and attach the following parts:

  • TitlePart
  • BodyPart
  • MenuPart

The type definition should look like this:

 

Create one ore more Page content items and RawPage content items and add them to the main menu.

 

Adding the CurrentContentWidget

Now let's add the CurrentContentWidget to see how it behaves. I've added it to the AsideSecond zone:

 

Viewing the CurrentContentWidget

When you navigate to the front end, you should be seeing a widget that displays a link to the current page:

 

Raw Page:

 

Summary

Sometimes one finds the need to access the current content item based on the current route/url.

We have seen how we can do so by quering the RouteData values and extract the "id" value if available.

We then discussed a potential usage for accessing the current content item, the obvious example being related articles, or perhaps even recommendations in an e-commerce site.

To make it easy to access the current content item, we implemented a reusable class called CurrentContentAccessor.

 

Download the source for this article: Skywalker.Utils.zip.

9 comments

  • ... Posted by Oleg Underwood Posted 11/07/2014 07:43 AM

    To comprehend how we can accomplish this, it is important comprehend how Orchard is able to map a url to a material product, so let's easily review that.

  • ... Posted by Maytham Fahmi Posted 11/07/2014 07:44 AM

    Sipke, cool article. Btw I am new started in Orchard and gets love in it. Any idea if I want to get a projection content Or/And queries/filtered content to view list?

    The code in this article is useful but was a little bit unsure how to get spicific content of defined queries filter.

    appreciate it

  • ... Posted by hegin Posted 11/07/2014 07:44 AM

    how can see last 5 content items?

  • ... Posted by StanleyGoldman Posted 11/07/2014 07:45 AM

    I found this solution to be the easiest... A Custom Content Handler like the one in this answer... http://stackoverflow.com/questions/14402243/orchard-filter-projection-query-based-on-another-module-field#answer-15089623

    And code in the view of the Widget like such... var contentItem = WorkContext.GetState<ContentItem>("CurrentContentItem");

  • ... Posted by pszmyd Posted 11/07/2014 07:47 AM [http://www.szmyd.com.pl]

    Yeah, but this is not the best solution for every scenario, as the availability of your "current" item depends on when the display shape gets created. Before that happens (eg. in the action filter OnActionExecuting method), the value will be null.

  • ... Posted by StanleyGoldman Posted 11/07/2014 07:48 AM [http://www.justaprogrammer.net]

    Good point, iirc in my use case I was looking to find the "CurrentContentItem" from a Widget's view.

  • ... Posted by Sipke Schoorstra Posted 11/07/2014 07:48 AM [http://www.ideliverable.com]

    Nice & easy, I like that one as well. Thanks for pointing it out.

  • ... Posted by Zoltán Lehóczky Posted 11/07/2014 07:50 AM [http://www.lombiq.com]

    In my Helpful Extensions module there is a Content.Current token for fetching the current content item, much like this: http://helpfulextensions.codeplex.com/SourceControl/changeset/view/7212c2603340#Extensions/Tokens/ContentTokens.cs

  • ... Posted by Sipke Schoorstra Posted 11/07/2014 07:50 AM [http://www.ideliverable.com]

    Good to know! And very smart to implement a token for that, could be useful. Initially I was about to use IAliasService as well, but I think the use of that is a bit limiting as it doesn't work for content types that don't have the AutoroutePart attached.

Leave a comment