Overriding ContentItem's Title in the dashboard

In my case, I have a Client content type that has a FirstName and a LastName property, that I would like to display as the title. Currently, my content overview looks like this:

 

 

As you can see, there is no easy way to see which client is which. One way to show some details about each client is by implementing the Display method of the ClientPartDriver, which would create a Shape, perhaps called Parts_ClientPart_SummaryAdmin. We would then configure this shape using Placement.info to be displayed when DisplayType is "SummaryAdmin". The overview would then look something like this:

 

Although this at least differentiates one client from the other, it would be even better if we could somehow change the "Client" (in blue) to display the name of the client.

One way to do this is simply attaching the TitlePart to the Client content type. However, the ClientPart itself already defines a FirstName and LastName property, which I would like to use instead.
As it turns out, Orchard provides a hook for us to do exactly that.

To see how this works, let's have a look at how Orchard renders the list of content items:

Orchard.Core/Contents/Views/Content.SummaryAdmin.cshtml (snippet):

<h3>@Html.ItemAdminLink(contentItem)</h3> - <div class="contentType">@contentItem.TypeDefinition.DisplayName</div>

 

As we can see, the template uses the ItemAdminLink HTML helper to render a link and the title of the content item.

Drilling down into that method, we see the following code:

Orchard.Framework/Mvc/Html/ContentItemExtensions.cs:

public static MvcHtmlString ItemAdminLink(this HtmlHelper html, string linkText, IContent content, object additionalRouteValues) {
            var metadata = content.ContentItem.ContentManager.GetItemMetadata(content);
            if (metadata.AdminRouteValues == null)
                return null;
 
            return html.ActionLink(
                NonNullOrEmpty(linkText, metadata.DisplayText, content.ContentItem.TypeDefinition.DisplayName),
                Convert.ToString(metadata.AdminRouteValues["action"]),
                metadata.AdminRouteValues.Merge(additionalRouteValues));
        }

 

Notice the call to html.ActionLink, where it either passes metadata.DisplayText or content.ContentItem.TypeDefinition.DisplayName (using the NonNullOrEmpty utility method).
Also notice that "metadata" is retrieved using a call to content.ContentItem.ContentManager.GetItemMetadata(content).

It's implementation looks like this:

Orchard.Framework/ContentManagement/DefaultContentManager.cs:

public ContentItemMetadata GetItemMetadata(IContent content) {
            var context = new GetContentItemMetadataContext {
                ContentItem = content.ContentItem,
                Metadata = new ContentItemMetadata()
            };
 
            Handlers.Invoke(handler => handler.GetContentItemMetadata(context), Logger);
 
            return context.Metadata;
        }

 

As we can see, it invokes the GetContentItemMetadata for all registered IContentHandler implementations (Handlers is typed IEnumerable<IContentHandler>).

So, as it turns out, all we have to do is create class that implements IContentHandler and its GetContentItemMetadata method.
However, instead of directly implementing this interface ourselves, Orchard comes with an abstract ContentHandler base class that implements this interface, saving us a lot of typing and other plumbing.

The implementation is as simple as this:

using MyModule.Models;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
 
namespace MyModel.Handlers
{
    public class ClientPartHandler : ContentHandler
    { 
        protected override void GetItemMetadata(GetContentItemMetadataContext context)
        {
            var clientsPart = context.ContentItem.As<ClientPart>();
 
            if (clientPart != null)
                context.Metadata.DisplayText = string.Format("{0} {1}", clientPart.FirstName, clientPart.LastName);
        }
    }
}

 

What we're doing here is overriding the GetItemMetadata method of the ContentHandler class. We receive a context, which holds a reference to the content item for which metadata is being requested.
We simply cast that using the As<T> extension method to our ClientPart. We need to check whether that succeeded or not, since Orchard will invoke our handler for all content items of all content types when rendering them.

If the part is not null, we set the DisplayText to whatever we want. In this case, we set it to some text that represents our client.

When we refresh the content items overview, we see the expected result:

 

And that's how we roll in the districts!

To recap, we looked at how to override the title that is being displayed in the Content Items overview of the dashboard without having to attach a TitlePart to my content type.
We did so by creating a content handler, setting the DisplayText of the metadata for the content items in question.

 

UPDATE

David Hayden pointed out that he also handled this subject in his post: http://www.davidhayden.me/blog/content-types-with-title-part-and-ititleaspect-in-orchard-1.3
His solution is to simply implement ITitleAspect. Although I already tried to do so, it doesn't work (on my machine). I may have forgotten something, but I'll have to investigate.
In any case, the solution would be simple: just implement ITitleAspect:

public class ClientPart : ContentPart<ClientPartRecord>, ITitleAspect
    {
        public string FirstName
        {
            get { return Record.FirstName; }
            set { Record.FirstName = value; }
        }
 
        public string LastName
        {
            get { return Record.LastName; }
            set { Record.LastName = value; }
        }
 
        public string Title
        {
            get { return string.Format("{0} {1}", FirstName, LastName); }
        }
    }

 

This doesn't work for me, unless I change the implementation of the TitlePartHandler from this:

using Orchard.ContentManagement;
using Orchard.ContentManagement.Handlers;
using Orchard.Core.Title.Models;
using Orchard.Data;
 
namespace Orchard.Core.Title.Handlers {
    public class TitlePartHandler : ContentHandler {
 
        public TitlePartHandler(IRepository<TitlePartRecord> repository) {
            Filters.Add(StorageFilter.For(repository));
            OnIndexing<TitlePart>((context, part) => context.DocumentIndex.Add("title", part.Title).RemoveTags().Analyze());
        }
 
        protected override void GetItemMetadata(GetContentItemMetadataContext context) {
            var part = context.ContentItem.As<TitlePart>();
 
            if (part != null) {
                context.Metadata.DisplayText = part.Title;
            }
        }
    }
}

 

To this:

using Orchard.ContentManagement;
using Orchard.ContentManagement.Aspects;
using Orchard.ContentManagement.Handlers;
using Orchard.Core.Title.Models;
using Orchard.Data;
 
namespace Orchard.Core.Title.Handlers {
    public class TitlePartHandler : ContentHandler {
 
        public TitlePartHandler(IRepository<TitlePartRecord> repository) {
            Filters.Add(StorageFilter.For(repository));
            OnIndexing<TitlePart>((context, part) => context.DocumentIndex.Add("title", part.Title).RemoveTags().Analyze());
        }
 
        protected override void GetItemMetadata(GetContentItemMetadataContext context) {
            var part = context.ContentItem.As<ITitleAspect>();
 
            if (part != null) {
                context.Metadata.DisplayText = part.Title;
            }
        }
    }
}

 

Which would make sense to me, as right now every content part that implements ITitleAspect will have it's Title property queried whenever this handler executes.

 

 

7 comments

  • ... Posted by J3ffb Posted 04/13/2012 10:34 AM

    Brilliant, that was on my list to look at.... thanks :)

  • ... Posted by Kevin Kuebler Posted 04/13/2012 01:14 PM [http://twitter.com/kevinkuebler]

    Nice post! I was actually going to demonstrate this technique in my "Advanced Orchard" course on Pluralsight. However I didn't do it due to a bug in Orchard 1.4.

    Sometimes you want to change the displayed title even when the content item does have the TitlePart on it. You may want to include another property along with the title for example. In my course, I used a Movie content type, which had the TitlePart on it for the movie title. Another property on the content item was the year the movie was released. When the movie content items were listed, I wanted to display the title and year (e.g. "Star Wars (1977)").

    You should be able to do this with exactly the technique you show here in this post. However it doesn't work in Orchard 1.4 because the Title handler always runs last and overwrites the DisplayText value with just the title value!

    Just something to be aware of. It's definitely a bug in 1.4, but I'm not sure about 1.3. I filed a bug for it though and the fix should be included in the 1.4.1 release soon.

  • ... Posted by Sipke Schoorstra Posted 04/13/2012 01:31 PM (Author)

    Thanks! Good to know about that issue. It's definitely going to be problematic if you have a TitlePart and want to vary it's Title. Perhaps we should be able to specify some sort of priority on our handlers to influence the order in which they are invoked. But that might not be optimal. Instead, maybe extend ITitleAspect and the TitlePartHandler with an extensibility hook of some sorts. We'll wait and see how it's solved in the next release.

    Anyway, thanks for the heads up!

  • ... Posted by Kevin Kuebler Posted 04/13/2012 05:41 PM [http://twitter.com/kevinkuebler]

    The fix has already been committed to the source repository for 1.x. I believe all you have to do is indicate in your Module.txt manifest that your module has a dependency on Title. That will ensure that your handler runs after the Title handler.

  • ... Posted by 2326220 Posted 04/17/2012 07:36 AM
    <p>a great job!the whole series help me a lot! <br>thanks a million.<br></p>
  • ... Posted by Millard Kelly Posted 04/21/2012 10:10 AM [http://www.tatvasoft.co.uk/]

    I was looking for such an information for quite a long, really good job done in this matter. Following this successfully, one will easily find a difference between two client which was not differentiable before.

  • ... Posted by Mobile Apps Development Posted 04/27/2012 06:56 AM [http://mindinventory.com/mobile_apps_dev.php]

    Usable lesson,come again for new lesson.

Leave a comment