Using a Tridion 2011 DataExtender to add a new metadata column to the GUI

DataExtenders are one of the coolest features in Tridion 2011 – and allow us to add our own column into the default Tridion ListView for all users.  We can save time when finding content – no need to open 10s of Components to find a specific value. However, with this new power comes a greater responsibility, and we must take extra care to make our code lightning-fast and test it well. In this example I will provide a working solution for adding a Metadata field column as well as explain how to do something similar yourself. All we need now is time and a little bit of luck to pull it off.

Tridion DataExtenders edit the response sent back from the GUI to the Browser.  In other words, our code runs every time for every user using the Tridion GUI.  DataExtenders are not limited to adding additional columns to the ListView.  Tridion uses this approach for the recent SDL World Server connector where they provide extra fields in the Schema edit screen, for example.  However, we’ll first start here on the ListView as it is the most useful GUI Extension for our scenario.

Tridion editors in my current project add the product sku to the Metadata field of each product.  When changing content or updating products they often have a product name or sku – and having this sku visible in the ListView provides a big time-saving.  The GUI also nicely provides the sort and filter functionality available from other columns to our new column.  However, to add this extra column our code will take time to find the sku value – and this will add a little bit to the response time of the GUI – especially for folders with lots of Components (100s).  So, it is a trade-off – will your users wait 1 second more for every 100 items in a folder if it means they can see the product Sku?  Do most of your folders contain less than 100 items?  In my case, yes, it is worth it, and we almost never have more than 100 items in a folder.

Example, note the Metadatafield column:

Metadata Column added to Tridion GUI

Getting started – Download the Example and Run the Code

1.  Get the example here: https://github.com/rcurlette/DataExtenderMetadataCol.  Open in Visual Studio 2010.

2.  Set the metadata fieldname.  Open the AddMetadataColumn.cs file, change ‘article_number’ to your fieldname.

3.  Compile.  Copy ALL files in the VS output folder (/bin/Debug) to the CMS Server at /Tridion/web/WebUI/WebRoot/bin.

4.  Create a new Folder on the CMS server for your DataExtender GUI Extension.  For example, create the DataExtender folder here: /Tridion/web/WebUI/Editors/DataExtender 

5.  Copy the DataExtender.config file to the folder above in step 4.

6.  Add the DataExtender config location to the System.config file in Tridion\web\WebUI\WebRoot\Configuration\System.config.

<editors default="CME">
  ...
  <editor name="DataExtender">
    <!-- DLL Files for DataExtender to be deployed to /Tridion/web/WebUI/WebRoot/bin -->
    <installpath>
     C:\Program Files (x86)\Tridion\web\WebUI\Editors\DataExtender\
    </installpath>
    <configuration>DataExtender.config</configuration>
    <vdir/>
  </editor>
</editors>

7.  Refresh the GUI and behold your new GUI ListView Column

Overview

How did we make that happen?  Well, DataExtenders are a special type of GUI Extension and allow us to modify the Response the GUI sends to the clients.  This is quite different than ContextMenu GUI Extensions.  Here our code is in ASPX / C#  while the ContextMenu code is mostly in JavaScript.  It feels like we’re hooking into a different aspect of the Tridion GUI and it requires a different approach – not only to how we develop, but also to how we test the code as well as how we debug and deploy.  Read on to find out how this was built and learn some tips and tricks for building your own DataExtenders.

Creating a new DataExtender

Speed is our primary concern when writing DataExtender code.   Not how fast you can type, but how fast your code executes.   The code is executed for every response – and we filter the response for the GetList command; all items in the listview.  Test the code with a folder containing hundreds of items – not only a few!  This cannot be overstated and is why I start with this disclaimer.  With the Tracing log feature you can view how much time your code is taking and adjust accordingly.  This is the most important and limiting aspect of developing a DataExtender since it affects all users and all content.

Create the .NET Class Project –  Create a new .NET Class Project and add a new Class file.

Extend the DataExtender Class – First we need to extend the DataExtender class.

public class AddMetadataColumn : DataExtender

Add the Namespace:

using Tridion.Web.UI.Core.Extensibility;

Reference the Assembly:  (located in Tridion/web/WebUI/WebRoot/bin)

Tridion.Web.UI.Core

Override the ProcessResponse Class

The ProcessResponse class is the main entry point for the DataExtender and is called for different types of GUI responses.  Notice here we check the command to see if it is for ‘GetList’.  This is critical for filtering out other requests and only listening to the listview.

This code is from a great example written by GUI Hacker Serguei Martchenko at http://www.sdltridionworld.com/community/2011_extensions/parentchangenotifier.aspx.

public override XmlTextReader ProcessResponse(XmlTextReader reader, PipelineContext context)
{
  XmlTextReader xReader = reader;
  string command = context.Parameters["command"] as String;
  if (command == "GetList")  // Code runs on every GetList
  {
    try
    {
      xReader = PreprocessListItems(reader, context);
...

Disclaimer: Any errors in the PreprocessListItems breaks the GUI.  If your XmlTextReader does not return at least the original data you will have missing data in the GUI.

 

ReCreate the <tcm:Item /> node, return as XmlTextReader

As mentioned above, we need to pass back at least the data that Tridion is sending back.  Thanks to the example code from Serguei we only need to follow a couple of simple rules.

1.  Re-write all existing attributes to the <tcm:Item node.

 xWriter.WriteAttributes(xReader, false);

2.  Add code and logic to get additional data.

attrValue = GetMetadataValue(comp, "article_number");

 

Getting the metadata value

In the example code I call a GetMetadataValue method for getting my data.

private string GetMetadataValue(Component comp, string fieldname)
{
  string value = "";
  xmlDoc.LoadXml(comp.GetXML(Tridion.ContentManager.Interop.TDSDefines.XMLReadFilter.XMLReadDataContent));
  string xPath = String.Format("//*[local-name()='{0}']", fieldname  );
  if (xmlDoc.SelectSingleNode(xPath) != null)
  {
    value = xmlDoc.SelectSingleNode(xPath).InnerText;
  }
  return value;
}
The performance criteria of the Metadata field example pushed me to use a TDSEWrapper instead of the Core Service.  The Core Service was quite slow – although I did not use Jaime’s approach with the tcp_Binding and instead used the default http_Binding.  I tried to create the WCF client once and re-use it, but maybe I did it wrong?  Anyways, it was the slowest of my examples.  Sergeui Martchenko also uses a TDSEWrapper for his code samples – maybe for the same reason?
Performance tests with folder of 250 Components:
Core Service 12 seconds
TDSE Object Model 12 seconds
TDSE GetXml 2 seconds

The load times are surprising and I would look to persisting Tridion data to an external system such as Redis or a database to improve performance times.  This cannot be emphasized enough – the Tridion API might not be fast enough – and you should seriously think about getting the data from another source.

3. Display the value with XPath in the DataExtender.config file

 <column xmlns=”http://www.sdltridion.com/2009/GUI/extensions/List”
                             id=”Metadatafield” type=”data” title=”Metadatafield”
                             selector=”@metadataFieldValue” translate=”String” />

Debugging – Enabling GUI Tracing

While writing this example I used the Trace debugging a lot – not only for timing certain actions – but also in my try/catch blocks for writing out errors.  This is the only way to Debug DataExtender code and it works well.  However, our log file grows REALLY fast – so you might not want to keep it running all the time.  In the C# code we can use Trace.Write to write output to the GUI Trace log.  Don’t use Trace.WriteLine – it doesn’t work.  This is also the best way to know our DataExtender is being executed. If we do not see the DataExtender name in the log file then it is not being loaded.

1.  Backup original file. Rename Tridion\web\WebUI\WebRoot\bin\Tridion.Web.UI.Core.dll to Tridion\web\WebUI\WebRoot\bin\Tridion.Web.UI.Core.dll.bak

2.  Copy Tridion.Web.UI.Core.dll Trace DLL from /trace to to /bin.  Location:  \Tridion\web\WebUI\WebRoot\bin\trace\Tridion.Web.UI.Core.dll

3.  Turn on Tracing in \Tridion\web\WebUI\WebRoot\Web.Config

<compilation debug="true" defaultLanguage="c#">

4.  Confirm Trace File is Created.  Refresh GUI, Trace log, Tridion.Web.trace, created in the folder  \Tridion\web\WebUI\WebRoot.

To delete Trace file – stop IIS (net stop w3svc) and then delete.

* Warning – This produces a LOT of log output – you do not want to do this in Production.

Example Trace code to output start time

Trace.Write("==========================Start PreprocessListItems " + System.DateTime.Now.ToShortDateString() + ", " + System.DateTime.Now.ToLongTimeString() + Environment.NewLine);
Trace File Output – Notice my GUI Extension is there ‘AddMetadataCol’.

Example Trace Code to display <tcm:Item XML

return xReader;  //around line 157
Trace.Write(sWriter.ToString() + Environment.NewLine);
// Trace.WriteLine breaks code

Quickstart:  Sample Empty DataExtender Class

If you are starting a fresh DataExtender you can use this class to get started instead of my  AddMetadataColumn.cs class.

 

This code is the basis of every DataExtender – and does nothing more than re-writing the XML output.

 

1.  Rename Example Classname to yours.

2.  Add your custom code to append content to the tcm:item node.

3.  Use Trace.Write to write to logTrace.Write(sWriter.ToString() + Environment.NewLine);

* Trace.WriteLine does NOT work – will break code.

4.  Update the Tridion Project References

using Tridion.ContentManager; // C:\Program Files (x86)\Tridion\bin\client
using Tridion.Web.UI.Core.Extensibility; // C:\Program Files (x86)\Tridion\web\WebUI\WebRoot\bin

 

Compile, Deploy, and Update DataExtender Config

As with all GUI Extensions – getting the right configuration is half the battle.

1. Copy the GUI Extension DLL and ALL other DLLs in the /Debug/bin (or /Release/bin) from the output of your VS build command to the Tridion Server folder \Program Files (x86)\Tridion\web\WebUI\WebRoot\bin

2. Add extension.config file to the /Editors/YourDataExtender folder. Check out the relationships between your Namespace, Classname, and AssemblyName for the config.

Take special note of the DLL Name (from the Project Properties window) and how it corresponds to the Config.  This was the most difficult part of writing the DataExtender for me.

type=”Namespace.Classname, Assemblyname” 

DataExtender Assembly Name
DataExtender Assembly Name
DataExtender Class File
DataExtender Class File

Tridion DataExtender Config fileTridion DataExtender Config File

3. Update the System.config file with the location of your config file

 

Add the DataExtender GUI Extension to the System Config

As soon as you do this your GUI Extension is ‘enabled’ and any errors / issues will be seen immediately in the GUI for all users.

Save in:  C:\Program Files (x86)\Tridion\web\WebUI\Editors\DataExtender\ DataExtender.config

Update the System.config file 

<editors default="CME">
  ...
  <editor name="DataExtender">
    <!-- DLL Files for DataExtender to be deployed to /Tridion/web/WebUI/WebRoot/bin -->
    <installpath>
     C:\Program Files (x86)\Tridion\web\WebUI\Editors\DataExtender\
    </installpath>
    <configuration>DataExtender.config</configuration>
    <vdir/>
  </editor>
</editors>

 

DataExtender Tips:

Be Fast, be very fast - Your code is going to slow down the GUI, no question about it. But, how fast can you make your code? This is a key factor to the success of rolling out your GUI Extension.

Document it –  your code will be executed for every list view in every Publication of the GUI. Knowing what is happening there will help all developers maintain it in the future. Flow diagrams are good here.

Use your own Dev Server –  I broke the GUI a lot of times before getting it to work. So – best to do this on your own local instance or in the middle of the night.

Troubleshooting:

Nothing shows in the lists – Comment out your extension in the System.config.  Does it work now?  Ok, you definitely broke it.

Write out the Tridion tcm:Item XML.  See line 166 in the AddMetadataColumn.cs file.

xWriter.WriteAttributes(xReader, false);

Make sure the default tcm:Item attributes are added the the XML.

attrValue = GetMetadataValue(comp, "article_number");

 

Summary

Big thanks to the Tridion R&D team for giving us the power to do this.  It allows us to mold the Tridion CMS for our own organizations and improve efficiency while saving time.  This  can be very handy.  Programming the DataExtender is quite simple and most of our effort is in the Tridion API code itself – something we should all be familiar with. I am very happy with the approach and possibilities. We are very lucky that the base code has been provided from Seguei and we only need to get our config correct and deploy the files. One last warning that your code will be executed for every GetList request, so if your code is not fast then it is not for this type of extension. I can imagine for some use-cases going to the Tridion API for each piece of data will be too time-consuming and you may need to persist that data to an external system such as Redis that is much faster.  Have fun and try to remember to be kind.

6 thoughts on “Using a Tridion 2011 DataExtender to add a new metadata column to the GUI

  1. Great stuff Robert.
    I’m quite surprised with your performance numbers, I’ve done some extensions using Core Service (net.tcp) and I get sub-second results with about 50 items right now (and I’m actually reading a field from the component content).

  2. Nuno – My guess is net.tcp is much faster than basic_HTTP? I can develop locally with basic_HTTP but net.tcp only runs on the server. My core service code is in the project – maybe you can try to access a metadata field using your net.tcp client for 250 or more items and see how fast it is?

  3. Hi
    I am trying to implement a Data Extender for Tridion 2011 SP1. In this version I am unable to find Tridion.ContentManager.Interop.cm_defines and Tridion.ContentManager.Interop.cm_tom . How to proceed with out them? And also in order reflect the list item values in our component do we need to have a list field? Please explain in detail, please provide tridion screen shots. Thanks in advance.

  4. Hi,

    The Interop DLLs are in the same folder as they have been since version 5.3. C:\Program Files (x86)\Tridion\bin\client\PIA

    Please note: These are deprecated in Tridion 2011 and expected to go away in Tridion 2012/2013. The Core Service is recommended to use for future-proof solutions. However, in my testing the Core Service was too slow. Although I was not using the better-performing TCP binding of the Core Service and instead using the slower HTTP binding.

    I do not understand your question about list values. The goal of this GUIU Extension is to expose values from anywhere (in my case a Component Metadata field) in the GUI Listview as its own column.

    Also, please be very careful with Data Extensions – you have the possibility to slow down the GUI for all users.

  5. I am trying to create a Data Extender which should fill the list field of my component taking the values from the look-up component. (Look-up file is nothing but another component which has one text field which is repeating. I need to fetch those values and populate in the drop down. )For that I am able to establish a core service connection just by adding a new binding of type basic http. I don’t know about service reference. Is it compulsory to have it? Can we populate a list field of the Tridion component through Data Extenders while creating the component page? How to point to a particular field of component page through Data Extenders to populate it? Please explain me. Thanks in advance.

  6. Hi Muralikrishna,

    I understand you want to change the behavior of the Component Edit screen and intercept the GUI Response to:
    – Get Another Component
    – Loop through a multi-valued text field
    – Populate the original Component with those text field values

    To me it sounds like a complicated solution – and I expect it to perform not so well. The ideal way to organize a list of items in Tridion is through a categories / keywords list. Is it possible to copy your component text fields to a keyword list?

    Otherwise, I am afraid I cannot help you – I have not yet practiced programming a GUI extension with a Component Edit screen DataExtender.

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=""> <strike> <strong>