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:
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; }
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
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);
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”
Tridion 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.