Posts Tagged ‘C#’
Publication URL TBB
In most cases, you will not be publishing the binary files (images/css/js) in the same location of where you publish pages. As a good practice, these external files will be stored in separate folders. In such cases, you will need to specify path (relative) in the page DWT template.
Generally external css files will be declared like
<link href="/styles/main.css" rel="stylesheet" type="text/css"/>
This would work only if you have styles folder immediately after server path.
http://<server>/styles/main.css
In Tridion, default publication URL is “/” but if you have specified any path in the Publication, you will need to append that path also. It should be accessed like.
http://<server>/<pub url>/styles/main.css
The below C# TBB will get the publication URL of the current publication.
//gets the current session object
Session session = engine.GetSession();
//gets page object from the package
Item item = package.GetByType(ContentType.Page);
//gets TcmUri object of Page
TcmUri tcmObj = new TcmUri(item.GetValue("ID"));
//generate tcm string format with publication id
string publicationId = string.Format("tcm:0-{0}-1", tcmObj.PublicationId);
//gets publication object from the current session
Publication publication = new Publication(new TcmUri(publicationId), session);
//writes the publication URL into the package
package.PushItem("PublicationUrl", package.CreateStringItem(ContentType.Text, publication.PublicationUrl));
As you aware, variable “PublicationUrl” will be included in the package so this variable is accessible when you include this TBB on your page template.
Now you need to call this variable on the page DWT like below,
<link href="@@PublicationUrl@@/styles/main.css" rel="stylesheet" type="text/css"/>
Get object types in C#/Tridion 2009
In C#, the general usage to identify the object type is using “is” keyword. This is okay to some extent but you have to write “switch” of “if”, for multiple types. If there are more object types, code will be lengthy. Recently I came across a easy way of checking the object’s type. This is helpful when the object is of COM type.
Just add the below reference in your project and call its TypeName method.
using Microsoft.VisualBasic;
.
.
.
.
.
public string GetTypeOf(object obj)
{
return Microsoft.VisualBasic.Information.TypeName(obj);
}
Output:
This was very handy for me in my migration project where I needed to write all the object details in log file.
Iterate all Inner Exceptions in C#
In simple applications or pages, handling an exception will not be a difficult task. But when your application is using any web services or threading, most of the time, actual exception details will be stored in InnerException of the main Exception object. In such cases, we need to iterate all the inner exceptions to get the actual error message.
Here is a code snippet to iterate all inner exceptions from the root Exception object.
private List<string> GetAllErrMessages(Exception ex)
{
List<string> messages = new List<string>();
//assign the current exception as first object and then loop through its
//inner exceptions till they are null
for(Exception eCurrent = ex.Exception; eCurrent != null; eCurrent = eCurrent.InnerException)
{
messages.Add(eCurrent.Message);
}
}
My team said they never saw any code like handling Exception objects using for loop. I don’t know but I learnt this from my lead when I was working on Adrenalin HRMS product. Just sharing the code for your review
Trigger mail from Tridion Workflow
One of the most-wanted requirement from all customers is that notifying users through email. When they have some level of approvers to approve the content before they get published, the current user or next level approvers can be notified via mail.
I’ve earlier done this same implementation in ASP.Net & MOSS but Tridion is bit different. Sending mail is no different than traditional C# way. C# is to write mail related code and VB Script is to invoke it. Thats it…
Tridion allows this implementation in 3 simple steps. This simple example is just to understand its process… other business logics can be included later according to our requirements.
Implementation
1) Create custom dll using C#
2) Create workflows using Visio
3) Define workflow actions using VB Script
1) Create Custom dll using C#
Assuming that you now have fair idea about developing custom assemblies in Tridion. I’m starting directly with a code snippet.
Let’s create a custom class to define a C# method which has mailing piece of code.
namespace WorkFlow
{
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.ProgId("WCMReq.WorkflowSamples")]
public class MailUtility
{
public bool NotifyUser(object workItem)
{
try
{
If(workItem is Component)
{
Component comp = (Component)workItem;
String message = “Current approval level of “ + comp.Title + “ is “ + comp.ApprovalStatus.Position;
return SendMail(message);
}
return false;
}
catch (Exception ex)
{
WriteLog(ex.Message);
}
}
private bool SendMail(string message)
{
…
}
}
}
2) Create workflows using Visio
Assuming that you are already defined workflows using Visio Tridion extention.
3) Define workflow actions using VB Script
Open any workflow automatic action and include the following VB Script code.
Dim workobj
Set workobj= CreateObject("WCMReq.WorkflowSamples")
workobj.NotifyUser(CurrentWorkItem.GetItem())
Set workobj= Nothing
Source Code for COM Deployer
In my earlier post, I had published COM Deployer which is just to copy the dlls into Tridion System. Here I’m briefing the source code of the tool …
Namespaces:
using System; using System.Configuration; using System.IO; using COMAdmin;
All these are common .Net namespaces excep COMAdmin. For this , you need to include Interop.COMAdmin.dll reference. This is common dll which is provided by Microsoft to access COM services on Windows OS. More details on this can be found in MSDN.
But if you don’t have time to go through all these stuffs, you can simply copy the file from the following directory on any Windows OS J
%windir%/System32/com/comadmin.dll
Configuration Entries:
I’ve stored these entries app.config file. Hope I don’t need to explain more about these configuration entries.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!--COM Service Name ex) "Tridion Content Manager" -->
<add key="application" value="Tridion Content Manager"/>
<!--Server name or IP where COM services are installed -->
<add key="server" value="192.168.0.2"/>
<!--Source file location (local/remote shared path), including file name & use ; to include multiple files-->
<add key="sourceFiles" value="D:\Training\SDL Tridion\wkflw.dll;D:\Training\SDL Tridion\evntSystem.dll "/>
<!--Target file location (local), without file name-->
<add key="targetLocation" value="C:\Program Files\Tridion\bin"/>
</appSettings>
</configuration>
Source Code:
//read config entries to local variables string server = ConfigurationManager.AppSettings["server"]; string application = ConfigurationManager.AppSettings["application"]; string sourceFiles = ConfigurationManager.AppSettings["sourceFiles"]; string targetLocation = ConfigurationManager.AppSettings["targetLocation"];
COMAdminCatalog is the root object to access the sevice. Please note that the user must be having admin access to run this tool otherwise you will get Access Denied error.
COMAdminCatalog catalog = new COMAdminCatalogClass(); //connecting to server to access COM services catalog.Connect(server); //stopping service catalog.ShutdownApplication(application);
COMAdminCatalog is an interface so we need to instantiate with COMAdminCatalogClass object.
//copy file to target location File.Copy(file, targetLocation + "\\" + file.GetFileName(), true);
Just copying the source files to target location.
//restarting service catalog.StartApplication(application);
After copied all the files, the services will be restarted by calling StartApplication method.
Sub methods:
Other private methods used in this utility…
/// <summary>
/// Method to write the output to the user
/// </summary>
/// <param name="message"></param>
private static void WriteMessage(string message)
{
Console.WriteLine(Environment.NewLine + message);
}
public static class FileExtentionClass
{
/// <summary>
/// This extention method will be accessible from any string object.
/// Intention is to retrieve the file name alone from the file path
/// </summary>
/// <param name="filePath">file path of string type</param>
/// <returns>file name of string type</returns>
public static string GetFileName(this string filePath)
{
return filePath.Substring(filePath.LastIndexOf("\\") + 1);
}
}
Get CData from XML using C#
<?xml version="1.0" ?>
<tcm:Error xmlns:tcm="http://www.tridion.com/ContentManager/5.0" ErrorCode="80040327" Category="19" Source="Kernel" Severity="2">
<tcm:Line ErrorCode="80040327" Cause="false" MessageID="16137">
<![CDATA[
Unable to save Keyword (tcm:0-0-0).
]]>
<tcm:Token>tcm:0-0-0</tcm:Token>
</tcm:Line>
<tcm:Line ErrorCode="80040327" Cause="true" MessageID="15176">
<![CDATA[
XML validation error. Reason: enumeration constraint failed. The element: '{uuid:FAC1CA6A-C5BC-4CEA-AA98-D11E5EE4CD3C}State' has an invalid value according to its data type."; Source: <State>TAMILNADU</State>;
]]>
<tcm:Token>enumeration constraint failed." The element: '{uuid:FAC1CA6A-C5BC-4CEA-AA98-D11E5EE4CD3C}State' has an invalid value according to its data type. "</tcm:Token>
<tcm:Token><State>TAMILNADU</State></tcm:Token>
</tcm:Line>
<tcm:Details>
<tcm:CallStack>
<tcm:Location>XMLValidation.ValidateXML</tcm:Location>
<tcm:Location>XMLValidation.ValidateXML</tcm:Location>
<tcm:Location>XMLValidation.ValidateCustom</tcm:Location>
<tcm:Location>KeywordBL.Create</tcm:Location>
<tcm:Location>XMLState.Save</tcm:Location>
<tcm:Location>Keyword.Save</tcm:Location>
</tcm:CallStack>
</tcm:Details>
</tcm:Error>
public List<string> GetCDataFromXML(string xmlFilePath)
{
//initialize output object
List<string> result = new List<string>();
//initialize XElement object with xml file path
XElement nodes = XElement.Load(xmlFilePath);
// In case of xml string instead of file path
//XElement nodes = XElement.Parse(xmlFilePath);
//navigates only CData elements
foreach (var cData in nodes.DescendantNodes().OfType<XCData>())
{
result.Add(cData.Value);
}
//returns collection of CData<string>
return result;
}
public List<string> GetCDataFromXML(string xmlFilePath)
{
//initialize output object
List<string> result = new List<string>();
//initialize xml reader object with xml file path
XmlReader reader = new XmlTextReader(xmlFilePath);
// In case of xml string instead of file path
// XmlReader reader = XmlReader.Create(new StringReader(xmlFilePath));
reader.MoveToContent();
//navigates only CData elements
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.CDATA:
result.Add(reader.Value);
break;
}
}
//returns collection of CData<string>
return result;
}
public class TCMError
{
List<string> _messages = null;
public string Message
{
get
{
//this is as per my requirement
return this._messages[this._messages.Count - 1];
}
}
public TCMError(string exception)
{
this._messages = new List<string>();
XmlReader reader = new XmlTextReader(exception);
reader.MoveToContent();
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.CDATA:
this._messages.Add(reader.Value);
break;
}
}
}
}
TCMError tcmError = new TCMError(exception);
Console.WriteLine("Message : {0}", tcmError.Message);
Getting keywords from category in Tridion
As part of my requirements, I needed to navigate all the keywords from all Tridion categories. So I have written a utility class which will loop through all the keywords in a publication. Here I have given a part of it which will explain about keyword retrieval part.
In Tridion, a category will contain all the keywords so in order to retrieve all the keywords, we need to have category object first. Category object does provide a method, GetKeywordByTitle, to retrieve a keyword by passing its title as parameter. But the problem is when you pass a wrong name to this method, this will throw an exception instead of returning null.
If you are familiar with .Net/JAVA programming, while you are retrieving an item from a collection, you will always expect null to be returned if an item does not exists. Hope this will be solved in Tridion 2011 J
So I’ve added a utility method which would return null even if the keyword doesn’t exists in the category.
private Keyword GetKeywordByTitle(Category catObj , string keyword)
{
try
{
return catObj.GetKeywordByTitle(keyword);
}
catch
{}
return null;
}
The above method can be called when an keyword object is going to retrieved as shown below.
private void TestMethod()
{
TDSE tObj = new TDSE();
Category catObj = (Category)tObj.GetObject("tcm:50-51-512", EnumOpenMode.OpenModeEdit, PublicationTCMURI, XMLReadFilter.XMLReadAll);
Keyword kObj = this.GetKeywordByTitle(catObj, “Chennai”);
if (kObj == null)
Console.WriteLine(“{0} does not exists!”, keyword);
else
Console.WriteLine(“{0} exists!”, keyword);
}
This is not a solution but just a tip for Tridion beginners J
Create/Import child keywords in Tridion
In my earlier post, we have seen about adding keywords. After that I was trying to add child keywords in the same code sample. But things didn’t work out when it comes to add parent details. Because you can’t assign multiple parents to a keyword if you use Tridion GUI but it is possible if you use Tridion Object Model. Finally after some trial and errors, I got the working piece of code.
If you are trying to import large data into Tridion, you can modify this code using recursion. I assume that you are familiar with .Net programming so I don’t want to confuse this code sample by adding recursion. This simple code is about adding child keyword to a parent.
When you are adding the child keywords, you need to make four changes in my previous code.
1) Instantiate Category object to fetch parent key object
2) Get Parent keyword object
3) IsRoot – should be set to false as this is not a root keyword
4) Should include <tcm:ParentKeyword> within <tcm:ParentKeywords> as shown below
//initializing tridion base object
TDSE TObj = new TDSE();
//creating category object
Category catObj = (Category)TObj.GetObject(CategoryTCMURI, EnumOpenMode.OpenModeEdit, PublicationTCMURI, XMLReadFilter.XMLReadAll);
Keyword parentKey = catObj.GetKeywordByTitle("India");
//getting new Tridion keyword object
//CategoryTCMURI - TCM uri of category object
//PublicationTCMURI - TCM uri of publication
Keyword KObj = (Keyword)TObj.GetNewObject(ItemType.ItemTypeKeyword, CategoryTCMURI, PublicationTCMURI);
string title = "Chennai";
string description = "Chennai is the 4th largest metro";
string key = "Chennai";
bool isAbstract = false;
bool isRoot = false;
StringBuilder keyXML = new StringBuilder();
keyXML.Append("<tcm:Keyword xmlns:tcm='http://www.tridion.com/ContentManager/5.0'>");
keyXML.Append("<tcm:Data xmlns:tcm='http://www.tridion.com/ContentManager/5.0'>");
keyXML.AppendFormat("<tcm:Title>{0}</tcm:Title>", title);
keyXML.AppendFormat("<tcm:Description>{0}</tcm:Description>", description);
keyXML.AppendFormat("<tcm:Key>{0}</tcm:Key>", key);
keyXML.AppendFormat("<tcm:IsAbstract>{0}</tcm:IsAbstract>", isAbstract.ToString().ToLower());
keyXML.AppendFormat("<tcm:IsRoot>{0}</tcm:IsRoot>", isRoot.ToString().ToLower());
keyXML.Append("<tcm:ParentKeywords xmlns:tcm='http://www.tridion.com/ContentManager/5.0' xmlns:xlink='http://www.w3.org/1999/xlink' >");
if (parentKey != null)
{
string info = parentKey.Info.Path;
info = info.Replace(" ", "%20");
info = info.Replace("\\", @"/");
keyXML.Append("<tcm:ParentKeyword xlink:type='simple' xlink:title='" + parentKey.Title + "' xlink:href='/webdav" + parentKey.Info.Path + "/" + parentKey.Title + ".tkw' /> ");
}
keyXML.Append("</tcm:ParentKeywords>");
keyXML.Append("<tcm:RelatedKeywords xmlns:tcm='http://www.tridion.com/ContentManager/5.0' xmlns:xlink='http://www.w3.org/1999/xlink' />");
keyXML.Append("<tcm:MetadataSchema xmlns:xlink='http://www.w3.org/1999/xlink' xlink:type='simple' xlink:title='' xlink:href='tcm:0-0-0' />");
keyXML.Append("<tcm:Metadata />");
keyXML.Append("</tcm:Data>");
keyXML.Append("</tcm:Keyword>");
KObj.UpdateXML(keyXML.ToString());
KObj.Save(true);
Here’s the output
Create/Import keywords in Tridion
Last week I had a chance to work on a custom tool to create Tridion keywords. I started doing some POCs using Tridion business connecter but it failed so I tried again with Object Model (TOM). I was sure that this could be done through object model but there were no much details available in Tridion materials. We had tried various options but finally support team have helped us to find a quick way to add keywords.
Solution:
Keywords can be created/edited by updating its intermediate xml. But I don’t have a complete knowledge on this intermediate xml format as Tridion is using this for internal processing. But it worked for creating keywords.
Here’s a sample C# code to create a keyword in Tridion…
//initializing tridion base object
TDSE TObj = new TDSE();
//getting new Tridion keyword object
//CategoryTCMURI - TCM uri of category object
//PublicationTCMURI - TCM uri of publication
Keyword KObj = (Keyword)TObj.GetNewObject(ItemType.ItemTypeKeyword, CategoryTCMURI, PublicationTCMURI);
string title = "Books";
string description = "Tamil Books";
string key = "Books";
bool isAbstract = false;
bool isRoot = true;
StringBuilder keyXML = new StringBuilder();
keyXML.Append("<tcm:Keyword xmlns:tcm='http://www.tridion.com/ContentManager/5.0'>");
keyXML.Append("<tcm:Data xmlns:tcm='http://www.tridion.com/ContentManager/5.0'>");
keyXML.AppendFormat("<tcm:Title>{0}</tcm:Title>", title);
keyXML.AppendFormat("<tcm:Description>{0}</tcm:Description>", description);
keyXML.AppendFormat("<tcm:Key>{0}</tcm:Key>", key);
keyXML.AppendFormat("<tcm:IsAbstract>{0}</tcm:IsAbstract>", isAbstract.ToString().ToLower());
keyXML.AppendFormat("<tcm:IsRoot>{0}</tcm:IsRoot>", isRoot.ToString().ToLower());
keyXML.Append("<tcm:ParentKeywords xmlns:tcm='http://www.tridion.com/ContentManager/5.0' xmlns:xlink='http://www.w3.org/1999/xlink' />");
keyXML.Append("<tcm:RelatedKeywords xmlns:tcm='http://www.tridion.com/ContentManager/5.0' xmlns:xlink='http://www.w3.org/1999/xlink' />");
keyXML.Append("<tcm:MetadataSchema xmlns:xlink='http://www.w3.org/1999/xlink' xlink:type='simple' xlink:title='' xlink:href='tcm:0-0-0' />");
keyXML.Append("<tcm:Metadata />");
keyXML.Append("</tcm:Data>");
keyXML.Append("</tcm:Keyword>");
KObj.UpdateXML(keyXML.ToString());
KObj.Save(true);
Note:
Lowercase values should be assigned isRoot & isAbstract.
Get SID from LDAP Account Name
During my migration we came across a situation where we need SID but all we had only LDAP user name. So after few trail and error googling, below code snippet helped us to get SID from LDAP user name.
/// <summary>
/// Gets the name of the SID by account.
/// <summary>
/// <param name="accountName"> Name of the account.</param>
/// <returns>SID</returns>
public string GetSIDByAccountName(string accountName)
{
//name should be in the format of domain\username
WindowsIdentity userIdentity = new WindowsIdentity(accountName);
if (userIdentity == null)
throw new Exception(string.Format("{0} : User does not exists", accountName));
return userIdentity.User.AccountDomainSid.ToString();
}
Output : S-1-5-21-789336058-507921405-854245398-9938

