Using helpers in Tridion Razor templating
Today, for the first time, I used a helper in a Razor Tridion template. I'd made a fairly standard 'generic link' embedded schema, so that I could combine the possibility of a component link and an external link in a link list, and allow for custom link text. (Nothing to see here, move along now please.) However, when I came to template the output, I wanted to have a function that would process an individual link. A feature of Razor templating is that you can define a @helper, which is a bit like a function, except that instead of a return value, the body is an exemplar of the required output. There is also support for functions, so to lift Alex Klock's own example:
@functions { public string HelloWorld(string name) { return "Hello " + name; } }
and
@helper HelloWorld(string name) { <div>Hello <em>@name</em>!</div> }
will serve fairly similar purposes.
What I wanted to do today, however was slightly different; I didn't want to pass in a string, but a reference to my embedded field. All the examples on the web so far are about strings, and getting the types right proved interesting. I started out with some code like this:
@foreach(var link in @Fields.links){ @RenderLink(link); }
So I needed a helper called RenderLink (OK - this might be a very trivial use-case, but a real problem all the same.). But what was the type of the argument? In theory, "links" is an EmbeddedSchemaField (or to give it it's full Sunday name: Tridion.ContentManager.ContentManagement.Fields.EmbeddedSchemaField) but what you get in practice is an object of type "Tridion.Extensions.Mediators.Razor.Models.DynamicItemFields". I'd already guessed this by poking around in the Razor Mediator sources, but after a few of my first experiments went astray, I ended up confirming that with @link.GetType().FullName
Well I tried writing a helper like this:
@using Tridion.Extensions.Mediators.Razor.Models @helper RenderLink(DynamicItemFields link){ ... implementation }
but that didn't work, because when you try to call the methods on 'link' they don't exist.
And then, just for fun, of course, I tried
@using Tridion.ContentManager.ContentManagement.Fields @helper RenderLink(EmbeddedSchemaField link){ ... implementation }
but that was just going off in an even worse direction. Yeah, sure, that type would have had the methods, but what I actually had hold of was a DynamicItemFields. Eventually, I remembered some hints in the mediator's documentation and tried using the 'dynamic' keyword. This, it turns out, is what you need. The 'dynamic' type lets you invoke methods at run-time without the compiler needing to know about them. (At last, I was starting to understand some of the details of the mediator's implementation!)
@helper RenderLink(dynamic link){ ... implementation }
This may be obvious with hindsight (as the old engineers' joke has it ... for some value of 'obvious') . For now, I'm writing another blog post tagged #babysteps and #notetoself, and enjoying my tendency to take the road less travelled.
TWO roads diverged in a yellow wood, | |
And sorry I could not travel both | |
And be one traveler, long I stood | |
And looked down one as far as I could |
|
To where it bent in the undergrowth; | |
Then took the other, as just as fair, | |
And having perhaps the better claim, | |
Because it was grassy and wanted wear; |
|
Though as for that the passing there | |
Had worn them really about the same, | |
And both that morning equally lay | |
In leaves no step had trodden black. |
|
Oh, I kept the first for another day! | |
Yet knowing how way leads on to way, | |
I doubted if I should ever come back. | |
I shall be telling this with a sigh |
|
Somewhere ages and ages hence: | |
Two roads diverged in a wood, and I— | |
I took the one less traveled by, | |
And that has made all the difference. |
-- Robert Frost