Skip to content. | Skip to navigation

Personal tools

Navigation

You are here: Home / weblog

Dominic Cronin's weblog

Showing blog entries tagged as: Tridion

Templating unbalanced tags in the Razor Mediator

Posted by Dominic Cronin at Nov 17, 2012 05:30 PM |

I've recently started using the Razor Mediator for Tridion (http://code.google.com/p/razor-mediator-4-tridion/) on a project, and it's been an interesting experience. To be honest, I wondered at first whether it would shift my views further in the direction of putting code in the templating layer, but I suspect I'll probably remain a die-hard token replacer. I did start at first with writing rather more C# in my templates than I generally would, but the reality is that the complexity always increases, and pretty soon you find yourself wanting to debug the code in Visual Studio. Then I'd rather have it in an assembly of its own. (OK - maybe there are, or could be, techniques for debugging your code in-place in the Razor template, but I'm not sure if the game would be worth the candle.)

Having said that, a few simple loops and if-blocks should be perfectly OK in the templating layer, which brings me to the subject of this post. My design has a page template which manages a list, in which the <li/> elements are created by a component template. The responsibility for the <ul/> belongs in the page template. (Yes - I know, but I've thought about it, and for what I'm trying to do, this is what makes the most sense.) So what about the scenario where they don't place any of the relevant component presentations on the page? Then I don't want the <ul> or the </ul> either. So I looked at the examples, and found how to do an if-block. How hard can it be, right? But this was where I hit another of my #babysteps learning points, which I'd like to share.

If you want to have an entire feature of your page appear or disappear based on a condition, you can simply write something like:

@if (someCondition) {
  <h1>The condition was met. Yeehah!</h1>
}

Straightforward enough: you can just put your desired html output in your block, and it appears or doesn't depending on the condition. And at this point I was in full-on how-hard-can-it-be hubris-mode, cruising for a bruising and headed for a fall. Ok - let's go:

@{
  var documents = @GetComponentPresentationsByTemplate("My Documents CT");
}
@if (documents.Count > 0) {
 <ul class="lookListy"> 
}
@foreach (var cp in documents) {
  @cp.RenderComponentPresentation()
}			          			}
@if (documents.Count > 0) {
 </ul> 
}

... or something similar. Looks reasonable, eh? (OK - maybe with a bit of practice I can get that tidier.) Except it's not. It doesn't compile, or more specific, the C# generated by Razor doesn't compile, and in Tridion, all you see is a nasty message about the wrong number of curly brackets or semicolons or some such. It doesn't really matter much what the error is, because the structure of your code is broken, and the thing it's reporting is further down, and somewhere in the generated code anyway.

Nota Bene: This level of error reporting is reason enough to avoid doing any complex logic in your template. Put it in a class, for goodness' sake!

So what was the problem? It turns out that to put HTML in-line in a Razor block, the tags need to balance, so you can say

<ul>.... <./ul>

, but not an opening

<ul>

without the closing tag.

This is not an issue with razor-mediator-4-tridion per se, but rather one with the way Razor itself works. Still - to do a successful Razor templating implementation in Tridion, you'll almost certainly need to know it. The solution is simple: you just need to wrap your unbalanced tags in a <text/> wrapper, as follows:

@{
  var documents = @GetComponentPresentationsByTemplate("My Documents CT");
}
@if (documents.Count > 0) {
 <text><ul class="lookListy"></text> 
}
@foreach (var cp in documents) {
  @cp.RenderComponentPresentation()
}			          			}
@if (documents.Count > 0) {
 <text></ul></text> 
}

This will now compile correctly, and produce the desired result.

Thanks to the contributors over at http://code.google.com/p/razor-mediator-4-tridion/. It's a great project, and I can see lots of potential for using it in my own work. Much as I'm a fan of XSLT for other uses, in templating its verbosity tends to make people push important code out of view, and well... Dreamweaver syntax ain't pretty either. :-)

EDIT: Thanks to a suggestion by Neil Gibbons (Thanks Neil!) I now realise that if you nest the foreach inside the if (which works for the logic I was trying to achieve), the <ul/> is now seen as 'balanced' and doesn't need the <text.> wrapper. So the problem is less severe than I had thought, but it's still one you need to be aware of.

@{
   var documents = GetComponentPresentationsByTemplate("My Documents CT"); 
   if (documents.Count > 0) {
       <ul class="lookListy">
	 @foreach (var cp in documents) {
	   @cp.RenderComponentPresentation();
       }
       </ul>
    }
  }

Toggling the javascript minification of the Tridion GUI from the powershell

Posted by Dominic Cronin at Nov 12, 2012 11:50 PM |
Filed under: , ,

Most of the time, I use a single Tridion development image for multiple purposes, including whatever time I get to spend researching how to do GUI extensions. When I'm flipping back out of research mode into doing some day-to-day development such as templating, it's better to have the benefit of the javascript minification that I might prefer to switch off while poking around in the guts of Anguilla. So just to make this switch as painless as possible, I've added the following code to my powershell $profile.

function SetGuiMinification($value){
  $filename = 'C:\Program Files (x86)\Tridion\web\WebUI\WebRoot\Configuration\System.config'
  $conf = [xml](gc $filename)
  $conf.Configuration.filters.filter |?{$_.type -like '*JScriptMinifier*'} |%{$_.enabled = $value}
  $conf.Save($filename)
  iisreset
}

function guimin {SetGuiMinification "always"}
function guinomin {SetGuiMinification "never"}

Now I can toggle backwards and forwards simply by typing guimin or guinomin (you may favour different words or spellings!)

Of course, this technique ought to work just as well to manipulate other elements and attributes in the XML files that control a Tridion installation. Perhaps you'd modify it to toggle the CSS minification too (removing the -like clause should do it).

If you have any good ideas for using this technique, please let me know.

Context Bag - a Tridion templating pattern

Posted by Dominic Cronin at Oct 30, 2012 12:25 AM |
Filed under: ,

When Tridion introduced compound templating (or modular templating if you prefer) in R5.3, one of the things that gradually became apparent was that in the new approach, the relationship between page renders and component renders was rather different. In VbScript, we'd been used to having a fairly simple way to pass parameters between the two. You could read and write parameters from and to a kind of global scope. This meant you could have your component templates influence the way the page templates worked, or have one component template influence the outcome of other component templates that were invoked further down the page. In modular templating, all this was over. You had a Context Variables dictionary available to you in both kinds of render, but the Context Variables dictionary you got in the component context was a new dictionary populated with the values from the original Context Variables of the page.

Of course, most of the time, this model works great. If you have a need to go beyond its limits, the first thing you should do is have a good look at your design and evaluate whether what you're trying to do is really smart. But still - there are rare cases where it can be really useful to pass state back up from the component to the page, make it available to other component renders, etc. Well the good news is, it is possible - you just have to add one more layer of redirection. I've been telling people for ages that I thought this would be possible; I'd just never got round to proving it in code. Well now I have, and I've written up how to implement this pattern over at Tridion Practice. I hope most of you will never need to do it, because it adds another level of complexity, and mostly there's a better way. Anyway - either I hope some small number of you will find it useful, or perhaps I'm just trying to establish prior art in case Apple decide to patent it.

How to list all the component templates associated with a schema

Posted by Dominic Cronin at Sep 26, 2012 06:45 PM |
Filed under: , ,

This posting might seem a little trivial, but having figured it out, I'm blogging it for my own reference. In fact, I was almost going to put it on the Tridion cookbook, but this is legacy stuff. There won't ever need to be a core service version of this, because in 2011, you can get the answer directly from Where Used.

But on older systems, say you wanted to update a schema, and wanted to figure out the impact on your templates. Which templates would you have to check for necessary updates, etc? (Imagine you were going to make a mandatory field optional, and wanted to check whether your templates would break if the user chose not to give a value.)

So you know which schema it is, and you want to know the component templates that have this as a related schema. I started to hack this out in Powershell using what are now for me fairly standard techniques. The trouble is that VBA collections are difficult to iterate over in the Powershell. Fortunately you can use the contains method on the RelatedSchemas collection to get the "where" clause you need. In most systems, you keep your templates, schemas etc, in a "system" folder, so the script simply starts at that location, and recursively grabs all the component templates it can find, If the schema of interest is in the related schemas, it will be listed.

$tdse = new-object -com TDS.TDSE
$interestingSchema = $tdse.Getobject("tcm:10-1234-8",1)
$systemFolder = $tdse.GetObject("tcm:11-123-2",1)
$rf = $tdse.CreateListRowFilter()
$rf.SetCondition("Recursive", $true)
$rf.SetCondition("ItemType", 32)
([xml]$systemFolder.GetListItems(3, $rf)).ListItems.Item | ?{$tdse.Getobject($_.ID,1).RelatedSchemas.Contains($interestingSchema)}

A poor man's Component synchroniser - or using the .NET framework to run XSLT from the PowerShell

Posted by Dominic Cronin at Aug 12, 2012 07:10 PM |

Just lately, I've been doing some work on porting the old Component Synchroniser power tool to the current version of Tridion. If you are familiar with the original implementation, you might know that it is based on a pretty advanced XSLT transformation (thankfully, that's not the part that needs porting), that pulls in data about the fields specified by the schema (including recursive evaluation of embedded schemas), and ensures that the component data is valid in terms of the schema. Quite often on an upgrade or migration project, any schema changes can be dealt with well enough by this approach, but sometimes you need to write a custom transformation to get your old component data to match the changes you've made in your schema. For example, the generic component synchroniser will remove any data that no longer has a field, but if you add a new field that needs to be populated on the basis of one of the old fields, you'll be reaching for your favourite XSLT editor and knocking up a migration transform.

This might sound like a lot of work, but very often, it isn't that painful. In any case, the XSLT itself is the hard part. The rest is just about having some boilerplate code to execute the transform. In the past, I've used various approaches, including quick-and-dirty console apps written in C#. As you probably know, in recent times, I've been a big fan of using the Windows Powershell to work with Tridion servers, and when I had to fix up some component migrations last week, of course, I looked to see whether it could be done with the PowerShell. A quick Google led me (as often happens!) to Scott Hanselman's site where he describes a technique using NXSLT2. Sadly, NXSLT2 now seems to be defunct, and anyway it struck me as perhaps inelegant, or at the least less PowerShell-ish to have to install another executable, when I already have the .NET framework,, with System.Xml.Xsl.XslCompiledTransform, available to me.

I've looked at doing XSLT transforms this way before, but there are so many overloads (of everything) that sometimes you end up being seduced by memory streams and 19 flavours of readers and writers. This time, I remembered System.IO.StringWriter, and the resulting execution of the transform took about four lines of code. The rest of what you see below is Tridion code that executes the transform against all the components based on a given schema. Sharp-eyed observers will note that in spite of a recent post here to the effect that I'm trying to wean myself from the TOM to the core service, this is TOM code. Yup - I was working on a Tridion 2009 server, so that was my only option. The good news is that the same PowerShell/XSLT technique will work just as well with the core service.

$tdse = new-object -com TDS.TDSE

$xslt = new-object System.Xml.XmlDocument
$xslt.Load("c:\Somewhere\TransformFooComponent.xslt")
$transform = new-object System.Xml.Xsl.XslCompiledTransform
$transform.Load($xslt)
$sb = new-object System.Text.StringBuilder
$writer = new-object System.IO.StringWriter $sb
filter FixFooComponent(){
$sb.Length = 0
$component = $tdse.GetObject($_, 2)
$xml = [xml]$component.GetXml(1919)
$transform.Transform($xml, $null, $writer)
$component.UpdateXml($sb.ToString())
$component.Save($true)
}
$schema = $tdse.GetObject("/webdav/SomePub/Building%20Blocks/System/Schemas/Foo.xsd",1)
([xml]$schema.Info.GetListUsingItems()).ListUsingItems.Item | ? {$_.Type -eq 16}| %{$_.ID} | FixFooComponent

Why should your Tridion GUI extension 'model' have it's own service layer on top of the core service?

Posted by Dominic Cronin at Aug 08, 2012 06:49 PM |

I've spent some time lately looking at the architecture for the next phase of implementing the Component Synchroniser for the Tridion Power Tools project. This meant looking through most of the other power tools, because, of course, they are a great resource for anyone wanting to build a Tridion GUI extension. The down side of this is sometimes, reading the code, you can observe a pattern being used, but it can be hard to tell why this would be a good or bad design. I'd noticed that the model of pretty much every power tool is implemented as a WCF service, often acting as a very thin wrapper around the core service client. As I was wondering about this, I posed the following question in the private chat channel used by the Tridion MVPs and community builders:

So if you're doing a gui extension, is it reckoned to be bad form to access the core service directly from your aspx. Or is it just coincidence that most (all?) of the power tools have an additional service layer?

This was enough to spark quite an informative debate, and in keeping with the spirit of the thing, I promised to write it up for general consumption. The contributors were Frank van Puffelen, Nuno Linhares, Peter Kjaer and Jeremy Grand-Scrutton.

The general feeling was that you ought to stick to the pattern I had observed in the power tools. The reasons were as follows:

  • Ease of coding - The Anguilla framework can automatically generate a JavaScript proxy for your service.
  • Maintainability - if you talk directly from JavaScript to the core service, you will not get any compile-time checks, whereas your own service layer would be built in .NET and would therefore have some defences against future (likely) changes in the core service client.
  • Consistency with the rest of the CME - In the CME, views are typically considered fully client-side. Where the CME does use Aspx, this is only to generate some HTML on the server, and typically not to for implementation logic.
  • Known issues -  ASP.NET postbacks in Anguilla views have been known to cause problems for some people, since e.g. popups won't keep their state through a postback (or an F5 press for that matter).

 

According to  these criteria, the actual design I was looking at could use the core directly, as my idea was to generate some HTML. In practice, it turns out that there are other reasons to stick with the extra service layer. Even so, I'm very glad I asked the question, and that the answers I got were so informative. Thanks guys!

A thing of beauty is a joy for ever

Posted by Dominic Cronin at Jul 09, 2012 09:30 PM |

So - I've been using the Windows Powershell for the odd bit of Tridion work. You knew that.

And you probably also knew that very often the Tridion API hands you back a string representing an XML document, and that it's very convenient to "cast" this to a .NET XmlDocument using the [xml] operator. Just search on this blog for "powershell" and you'll find enough examples. But still - there's a missing piece in the puzzle. So today I wanted to look at the output from the .GetTridionWebSchemaXMl() method on a Tridion Object Model Schema object. (Don't worry - I am weaning myself off the TOM; I wanted to compare this API with the ReadSchemaFields() method on the core service client API.)

Anyway - for what it's worth, here's what the raw string looks like:

> $tdse.GetObject("tcm:21-509-8",1).GetTridionWebSchemaXML(1919,$true)
<tcm:TridionWebSchema ID="tcm:21-509-8" IsEditable="false" xmlns:tcm="http://www.tridion.com/ContentManager/5.0"><tcm:C
ontext xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:transform-e
xt="urn:tridion:transform-ext"><tcm:Publication xlink:type="simple" xlink:title="Synchroniser tests" xlink:href="tcm:0-
21-1" /><tcm:OrganizationalItem xlink:type="simple" xlink:title="TestSchemaOne" xlink:href="tcm:21-50-2" /></tcm:Contex
t><tcm:Info xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:transf
orm-ext="urn:tridion:transform-ext"><tcm:LocationInfo><tcm:WebDAVURL>/webdav/Synchroniser%20tests/Building%20Blocks/Tes
tSchemaOne/TestSchemaCategories.xsd</tcm:WebDAVURL><tcm:Path>\Synchroniser tests\Building Blocks\TestSchemaOne</tcm:Pat
h></tcm:LocationInfo><tcm:BluePrintInfo><tcm:OwningPublication xlink:type="simple" xlink:title="Synchroniser tests" xli
nk:href="tcm:0-21-1" /><tcm:IsShared>false</tcm:IsShared><tcm:IsLocalized>false</tcm:IsLocalized></tcm:BluePrintInfo><t
cm:VersionInfo><tcm:Version>3</tcm:Version><tcm:Revision>0</tcm:Revision><tcm:CreationDate>2012-07-07T18:28:23</tcm:Cre
ationDate><tcm:RevisionDate>2012-07-09T20:18:21</tcm:RevisionDate><tcm:Creator xlink:type="simple" xlink:title="TRIDION
DEV\Administrator" xlink:href="tcm:0-11-65552" /><tcm:Revisor xlink:type="simple" xlink:title="TRIDIONDEV\Administrator
" xlink:href="tcm:0-11-65552" /><tcm:ItemLock Title="No lock" Type="0" /><tcm:IsNew>false</tcm:IsNew></tcm:VersionInfo>
<tcm:AllowedActions><tcm:Actions Allow="1173513" Deny="102" Managed="0" /></tcm:AllowedActions></tcm:Info><tcm:Data xml
ns:xlink="http://www.w3.org/1999/xlink" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:transform-ext="urn:tr
idion:transform-ext"><tcm:Title>TestSchemaCategories</tcm:Title><tcm:Description>TestSchemaCategories</tcm:Description>
<tcm:Purpose>Component</tcm:Purpose><tcm:NamespaceURI>uuid:f14d60ed-0f7c-4d1f-a2e3-97d1dfeb1a1f</tcm:NamespaceURI><tcm:
RootElementName>Content</tcm:RootElementName><tcm:Fields><tcm:KeywordField><tcm:Name>ColoursOne</tcm:Name><tcm:Descript
ion>ColoursOne</tcm:Description><tcm:MinOccurs>1</tcm:MinOccurs><tcm:MaxOccurs>unbounded</tcm:MaxOccurs><tcm:Category x
link:type="simple" xlink:title="Colours" xlink:href="tcm:21-59-512" /><tcm:Size>1</tcm:Size><tcm:List Type="tree" /><tc
m:ExtensionXml xmlns="http://www.sdltridion.com/ContentManager/R6" /></tcm:KeywordField><tcm:SingleLineTextField><tcm:N
ame>Animals</tcm:Name><tcm:Description>Test field with locally declared list</tcm:Description><tcm:MinOccurs>1</tcm:Min
Occurs><tcm:MaxOccurs>1</tcm:MaxOccurs><tcm:Size>1</tcm:Size><tcm:List Type="select"><tcm:Entry>Horse</tcm:Entry><tcm:E
ntry>Haddock</tcm:Entry><tcm:Entry>Weasel</tcm:Entry></tcm:List><tcm:ExtensionXml xmlns="http://www.sdltridion.com/Cont
entManager/R6" /></tcm:SingleLineTextField></tcm:Fields><tcm:MetadataFields /><tcm:AllowedMultimediaTypes /><tcm:Compon
entProcess xlink:type="simple" xlink:title="" xlink:href="tcm:0-0-0" /></tcm:Data></tcm:TridionWebSchema>

Yeah - erm ... Okaayyyy. Great.

OK - so how about we do the cast?

 

> [xml]$tdse.GetObject("tcm:21-509-8",1).GetTridionWebSchemaXML(1919,$true)
TridionWebSchema
----------------
TridionWebSchema

 

Well - at least you can read it.. but seriously - also not super helpful if you just want to scan the XML with good-old-fashioned human eyeballs.

So what can we do? Well I got to the point where I actually typed the following into Google:

powershell pretty print xml

and the first hit was on Keith Hill's blog.  Keith had written a nice little function that looks like this:

function XmlPrettyPrint([string]$xml) {
    $tr = new-object System.IO.StringReader($xml)
    $settings = new-object System.Xml.XmlReaderSettings
    $settings.CloseInput = $true
    $settings.IgnoreWhitespace = $true
    $reader = [System.Xml.XmlReader]::Create($tr, $settings)
    $sw = new-object System.IO.StringWriter
    $settings = new-object System.Xml.XmlWriterSettings
    $settings.CloseOutput = $true
    $settings.Indent = $true
    $writer = [System.Xml.XmlWriter]::Create($sw, $settings)
    
    while (!$reader.EOF) {
        $writer.WriteNode($reader, $false)
    }
    $writer.Flush()
    
    $result = $sw.ToString()
    $reader.Close()
    $writer.Close()
    $result
}

A minute later, this function was in my Powershell profile (and I slightly altered the name and added an alias) so now I can do the following:

> ppx ([xml]$tdse.GetObject("tcm:21-509-8",1).GetTridionWebSchemaXML(1919,$true)).OuterXml
<?xml version="1.0" encoding="utf-16"?>
<tcm:TridionWebSchema ID="tcm:21-509-8" IsEditable="false" xmlns:tcm="http://www.tridion.com/ContentManager/5.0">
  <tcm:Context xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:transform-ext="urn:tridion:transform-ext">
    <tcm:Publication xlink:type="simple" xlink:title="Synchroniser tests" xlink:href="tcm:0-21-1" />
    <tcm:OrganizationalItem xlink:type="simple" xlink:title="TestSchemaOne" xlink:href="tcm:21-50-2" />
  </tcm:Context>
  <tcm:Info xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:transform-ext="urn:tridion:transform-ext">
    <tcm:LocationInfo>
      <tcm:WebDAVURL>/webdav/Synchroniser%20tests/Building%20Blocks/TestSchemaOne/TestSchemaCategories.xsd</tcm:WebDAVURL>
      <tcm:Path>\Synchroniser tests\Building Blocks\TestSchemaOne</tcm:Path>
    </tcm:LocationInfo>
    <tcm:BluePrintInfo>
      <tcm:OwningPublication xlink:type="simple" xlink:title="Synchroniser tests" xlink:href="tcm:0-21-1" />
      <tcm:IsShared>false</tcm:IsShared>
      <tcm:IsLocalized>false</tcm:IsLocalized>
    </tcm:BluePrintInfo>
    <tcm:VersionInfo>
      <tcm:Version>3</tcm:Version>
      <tcm:Revision>0</tcm:Revision>
      <tcm:CreationDate>2012-07-07T18:28:23</tcm:CreationDate>
      <tcm:RevisionDate>2012-07-09T20:18:21</tcm:RevisionDate>
      <tcm:Creator xlink:type="simple" xlink:title="TRIDIONDEV\Administrator" xlink:href="tcm:0-11-65552" />
      <tcm:Revisor xlink:type="simple" xlink:title="TRIDIONDEV\Administrator" xlink:href="tcm:0-11-65552" />
      <tcm:ItemLock Title="No lock" Type="0" />
      <tcm:IsNew>false</tcm:IsNew>
    </tcm:VersionInfo>
    <tcm:AllowedActions>
      <tcm:Actions Allow="1173513" Deny="102" Managed="0" />
    </tcm:AllowedActions>
  </tcm:Info>
  <tcm:Data xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:transform-ext="urn:tridion:transform-ext">    <tcm:Title>TestSchemaCategories</tcm:Title>
    <tcm:Description>TestSchemaCategories</tcm:Description>
    <tcm:Purpose>Component</tcm:Purpose>
    <tcm:NamespaceURI>uuid:f14d60ed-0f7c-4d1f-a2e3-97d1dfeb1a1f</tcm:NamespaceURI>
    <tcm:RootElementName>Content</tcm:RootElementName>
    <tcm:Fields>
      <tcm:KeywordField>
        <tcm:Name>ColoursOne</tcm:Name>
        <tcm:Description>ColoursOne</tcm:Description>
        <tcm:MinOccurs>1</tcm:MinOccurs>
        <tcm:MaxOccurs>unbounded</tcm:MaxOccurs>
        <tcm:Category xlink:type="simple" xlink:title="Colours" xlink:href="tcm:21-59-512" />
        <tcm:Size>1</tcm:Size>
        <tcm:List Type="tree" />
        <tcm:ExtensionXml xmlns="http://www.sdltridion.com/ContentManager/R6" />
      </tcm:KeywordField>
      <tcm:SingleLineTextField>
        <tcm:Name>Animals</tcm:Name>
        <tcm:Description>Test field with locally declared list</tcm:Description>
        <tcm:MinOccurs>1</tcm:MinOccurs>
        <tcm:MaxOccurs>1</tcm:MaxOccurs>
        <tcm:Size>1</tcm:Size>
        <tcm:List Type="select">
          <tcm:Entry>Horse</tcm:Entry>
          <tcm:Entry>Haddock</tcm:Entry>
          <tcm:Entry>Weasel</tcm:Entry>
        </tcm:List>
        <tcm:ExtensionXml xmlns="http://www.sdltridion.com/ContentManager/R6" />
      </tcm:SingleLineTextField>
    </tcm:Fields>
    <tcm:MetadataFields />
    <tcm:AllowedMultimediaTypes />
    <tcm:ComponentProcess xlink:type="simple" xlink:title="" xlink:href="tcm:0-0-0" />
  </tcm:Data>
</tcm:TridionWebSchema>

So what's my point? Well I have a couple:

  1. The Internet is great (by which I mean, the people of the Internet are great.). I could have written that function myself in about half an hour. But in practice I might not have had the energy to do so at 10pm on a working Monday. Thanks to Keith's willingness to share, I had my solution inside a minute - working and tested. How cool is that?
  2. Somehow, this has just taken a little bit of the friction out of my working day, not to mention my so-called free time. I can now pull data straight out of a method that returns string, and get human-readable XML. This stuff makes a difference.

 

Thanks Keith - and all the other Keith's out there.

P.S. Maybe even nicely formatted XML will never be a thing of beauty, so apologies to Keats.

Why is it really slow to access Tridion via webdav?

Posted by Dominic Cronin at Jun 01, 2012 07:02 PM |
Filed under: , , ,

Today I wanted to upload 20 or so image files to my Tridion server. This is a bit of a faff to do through the normal user interface. (You'd have to create multimedia components one by one and then upload the binaries individually.) But no problem, because you can always use WebDAV, right? I wanted to upload the images from the server, which runs Windows 2008 Server R2. OK - so where are we now? Erm... Computer.... right-click ... Map network drive.... Pick a letter.... http://localhost/webdav/ ....OK! Boom... there we are - a nicely mapped webdav drive.

But.... it was awful. Like wading knee-deep through treacle with all the acrobats of the Chinese state circus balanced on your head. Slow? I could have made a cup of tea while it opened a folder.

So what was going on? My first instinct was that it probably wasn't Tridion to blame. Something like this, that more or less renders the feature unusable would have been flushed out during product testing, and fixed. So let's start by blaming Windows! (Millions of Apple fan-persons and Linux-inhaling Bill-haters can't all be wrong eh?) Oh enough of that. Suffice it to say that a quick google took me to Mark Lognoul's blog, where he describes the solution to this problem on Vista or Seven. Does it work on Server 2008? Yup - works like a charm. Thanks Mark. Job's a good'un.

Getting to grips with the Tridion core service in Powershell

Posted by Dominic Cronin at Apr 01, 2012 05:05 PM |

As regular readers of this blog will know, I've been a long-standing fan of the Windows Powershell as a tool for interacting with Tridion. On more than one project, the flexibility of the Powershell has allowed me to process Tridion data in ad-hoc ways that would be unthinkable if you had to bring with you all the overhead of, say, C# and Visual Studio. All of that is, of course, positive, but the downside of it has been that I don't seem to be making the jump over to the core service, which, after all, I should expect to be one of my primary APIs for some time to come. So time to make a change.

A while ago, I had tinkered with using the TOM.NET API from the Powershell, but I stopped putting effort into that once I got the basics working. The advice from SDL is clearly to use the core service for the kind of scenarios that the Powershell covers. Just for the record, though - getting a TOM.NET session in the Powershell is considerably more difficult than the equivalent activity using the core service. To be fair, neither technique even remotely approaches the simplicity of "$tdse = new-object -com TDS.TDSE", but like I said, it's time to move on.

So once I started looking at this, I had a quick look at Frank van Puffelen's GetCoreServiceClientWithoutConfigFile recipe in the Tridion Cookbook, and then I spent some time snuffling around in Peter Kjaer's Tridion Powershell Modules. Both of these are great resources, but I suffer quite badly from Not Invented Here syndrome, so at the very least, I had to poke around a bit and see what's going on. After some blatant stealing: mostly from Peter's code, I ended up with this:

Add-Type -assemblyName System.ServiceModel
$binding = new-object System.ServiceModel.WsHttpBinding
$binding.MaxBufferPoolSize = [int]::MaxValue
$binding.MaxReceivedMessageSize = [int]::MaxValue
$binding.ReaderQuotas.MaxArrayLength = [int]::MaxValue
$binding.ReaderQuotas.MaxBytesPerRead = [int]::MaxValue
$binding.ReaderQuotas.MaxNameTableCharCount = [int]::MaxValue
$binding.ReaderQuotas.MaxStringContentLength = [int]::MaxValue
$endpoint = new-object System.ServiceModel.EndpointAddress http://localhost/webservices/CoreService2011.svc/wsHttp
Add-Type -Path 'C:\Program Files (x86)\Tridion\bin\client\Tridion.ContentManager.CoreService.Client.dll'
$core = new-object Tridion.ContentManager.CoreService.Client.SessionAwareCoreServiceClient $binding,$endpoint

So what's going on here? What I've extracted is pretty close to the barest minimum implementation I could get to. Maybe you could get it smaller if you weren't bothered by running up against the fairly low default quota values offered by the Windows Communication Framework. In fact, I'm quite unsure about my approach to the quotas. What I've done is effectively to say that quotas aren't helpful for my scenario, and set them all to the maximum possible. Does this make sense? Let me know what you think. (Edit: I asked a question about this on stackoverflow, and got some good answers.)

So - to use the service you need three things:

  1. The core service client assembly. (It's great that SDL are now shipping this with the product. This means I can mail you a script, and say "use the 'official' client", and expect it to work.)
  2. A System.ServiceModel.WsHttpBinding object
  3. A System.ServiceModel.EndpointAddress object

So we load the System.ServiceModel assembly using Powershell's Add-Type cmdlet. This assembly is part of the .NET framework so we can just ask for it by name. Later in the script we use the same cmdlet to load the client dll, but then we have to specify its location. Once we have System.ServiceModel loaded, we can instantiate a binding and an endpoint, and pass those to the constructor of the client. Even though we end up with a few lines of code, it's not really hard, eh?

From here on we can just use the $core object to talk to the service. To be honest, having had a bit of a dig into how it works, you're probably better off just using Peter's module, which takes care of more than my hard-coded version does, and also offers some utility methods, for example, to create a new user. In fact, assuming you have installed the module, getting started is even easier than instantiating a TDSE: just "$core = Get-TridionCoreServiceClient". Nice job, Peter. Thanks.

Edit: If you prefer a NetTcp binding, this is pretty simple too: Just instantiate the correct binding type:

$binding = new-object System.ServiceModel.NetTcpBinding

And use a different endpoint

$endpoint = new-object System.ServiceModel.EndpointAddress net.tcp://localhost:2660/CoreService/2011/netTcp

What should we hope for Tridion workflow?

Posted by Dominic Cronin at Mar 18, 2012 06:45 PM |
Filed under: ,

Over at Tridion Developer, Chris Summers posts today about the forthcoming Tridion Bundles feature, and how it may be bringing a revival to the fortunes of Tridion workflow. He says: "Now given the sparkle I have seen in people’s eyes whenever they say 'Bundles…', I am pretty confident that something great is coming in the next major release, and I thought it might be time resurrect the workflow debate in the community – if we get vocal about these things now, we may see some of our dreams sneaking in with the bundles."

Picking up on this thought, I've set out to attempt to articulate what my concerns with the existing implementation have been and what I hope for in any new approach. I don't know very much about the Bundles feature - it's all pretty much still under wraps over at Tridion HQ, so I'm not really going to cast my criticisms and suggestions in terms of Bundles. Maybe the Bundles team have already considered all these concerns, and their design covers the same ground in an entirely different way. I'm just going to put it in terms of how things are now, and you can extrapolate.

So what's wrong with workflow as it now stands? Well you'll get different answers depending on who you talk to. If you ask different people what Workflow features in a product like Tridion should offer, you'll generally get answers that fall roughly in to one of these categories:

  1. Something intended to enforce governance requirements, where some level of process automation is a necessary evil
  2. Something which is intended primarily to support process automation, and within which enforcing governance rules is one of several possible applications

On more than one occasion, I've heard criticisms of Tridion from people whose prior experience had led them to expect the latter. My defence of Tridion would usually come out of the notion that Tridion's workflow support is in the first category, and was never intended to be the second. (OK - if pressed, I have to accept that the literal meaning of the word Workflow is far closer to the second than the first) Now right there, you've already got a huge debate. If Chris asks: "Why can’t Tridion notify me when I have something to review", then perhaps he's suggesting that some process automation might be a good thing. Whatever...  Chris doesn't need me to put words in his mouth, and I'm sure he'll make his position clear in his promised expansions on the subject. For myself, I don't have a real problem if Tridion doesn't support process automation "out of the box", as long as it's not too hard to wire it up if that's what you want. If SDL chose to offer workflow support as a third-party integration, I'd be fine with that, although I suspect that the amount of architectural changes required might be significant. As a web content management system intended for enterprise customers, there will always be a proportion of those who need to implement governance rules, and for them, I suspect the built-in support would need to do at least this much, as the current implementation does.

In terms of the existing approach, the biggest problem is probably that workflow controls access to versions by riding on the back of the check-in mechanism. The check-in mechanism in turn is designed to ensure that when you are working on something, other people can't see the change until you check it in. When an item "goes in to" workflow, the item is checked-out to the system workflow user, and remains checked out until it leaves workflow. Intermediate saves don't get a new version number, but a "revision", and these revisions are visible to the current workflow assignee if they use the workflow features to access the item. At first sight, this seems like a great plan, because the workflow sub-system doesn't have to do a thing to manage the visibility of the version of the item. If only it were so simple.

Well almost: there's a second mechanism - approval statuses - whereby workflow controls which version of an item can be used for publishing. If this worked as you might intuitively imagine it worked, then it would be great. By using workflow, you can assign an approval status to an item. If you see that approval status, you know that the item has been through a specific workflow activity. Approval statuses are in an ordered list, and you can specify a Minimum Approval Status on a publication target. The fly in the ointment is that Tridion only respects these approval statuses while the item remains in workflow.

This combination of workflow items being checked-out, and an approval status mechanism that doesn't work outside workflow, adds up to some pretty tricky problems to solve if, for example, you want to have workflow on both pages and components. Especially for the end users, it can be particularly disturbing when the edit you have just made to a component disappears when you view the same thing from the page.

The basic idea behind approval statuses is a good one. Specifically, the idea that you can associate data with a given version of an item, and that this allows you to evaluate which version of an item ought to be used for a particular activity, or even more generally to drive any business rule. The key thing here is that the only way you can write that data is to ensure that the item goes through a specific workflow activity. (Apart from that last condition, doesn't this remind you of Application Data?) If this "workflow data" were to include some kind of visibility constraints, then maybe you could do away with the dependency on the check-out mechanism. If nothing else, removing that dependency would probably make it much more feasible to integrate with third-party tools.

OK - as I've said, I'm suggesting a radical re-architecture, and I'm quite sure that such things take far more consideration than it has taken me to knock out a quick blog article, As I understand it, the Bundles thinking is already fairly far developed, and as I've said, maybe they've taken a completely different view of things. Still - I think Chris is right to suggest that now is the time for us to be vocal about what we want. What if the workflow experience were smooth enough that we could consider using it for process automation? As things stand, we mostly end up advising people that workflow is a measure only to be used in cases of necessity. There is so much scope for a positive change here, that I'm really looking forward to the next generation of Tridion workflow, whatever that ends up looking like.