Future Orchard Part 2: more Tokens
This is part 2 for this post…
Before I show some more advanced features of Tokens, I should probably say a word about why exactly we think we need this feature. In a CMS, there are many places where you need to build strings from some static but configurable part and a number of values that come from the environment. In the first post in this series, I used the rather silly example of Mad Libs because I thought it was a fun and light-hearted way of explaining the technology. But obviously we are not building the feature to play silly word games, we are building it because we need it to build other cool stuff. Real applications include:
- e-mail formatting: this does not require full templating but always involves inserting values from various sources (sender, comment URL and text, site name, etc.). Message text should be easy to change by the site administrator.
- Content post-processing: one can imagine a lightweight post-processing phase on content item bodies, where tokens get dynamically replaced by their values. Having an extensible set of tokens enables modules to add their own stuff.
UPDATE: I forgot to mention that, but the way we insert images into body from the media picker could use a token that specifies the path of the image in a way that better survives content deployment to another server. - URL patterns: this is the Autoroute feature that I will detail in the next post. The idea is that the site administrator can specify an arbitrary pattern of URLs for a content type, without having to touch the code for the affected content types. For example, if you like the style of URLs on this blog, you could specify a pattern such as "/{Content.Container.Owner}/Blog/{Content.Date.Year}/{Content.Date.Month}/{Content.Date.Day}/{Content.Slug}" from the admin and get post URLs like: "/bleroy/Blog/2011/7/26/more-tokens".
- Many, many others, including your own.
In the URL example above, you may have noticed some deep tokens such as BlogPost.Date.Year. Those can be easily implemented using token chains:
context.For<IContent>("Content") .Token("Date", content => content.As<ICommonPart>().CreatedUtc.ToShortDateString()) .Chain("Date", "DateTime", content => content.As<ICommonPart>().CreatedUtc)
The date token itself is defined for any content item. If the token chain stops there (Content.Date), the short date string for the creation time of the content item is displayed. If there is more behind it (Content.Date.Year), then the creation date object is going to get fed again into token providers as a DateTime token for further resolution. This could work like this:
context.For<DateTime>("DateTime", () => DateTime.Now) .Token("Year", d => d.Year) .Token("Month", d => d.Month) .Token("Day", d => d.Day) /// ... and more
We have effectively described how to resolve the year, month and day tokens, not just the sub-tokens of Content.Date. This means that if I have a comment modification date token for example, as long as it targets "DateTime", this will be re-used.
Also note the default date value of Now if no DateTime exists on the context. This would enable me to do "The current year is {Date.Year}."
But what if I don't know the full list of token names people could use? What if, for example, I want the sub-token after a date to be the format to use on the date object? Well, you don't have to use the syntactic sugar for static tokens that I've used so far, and instead go dynamic:
context.For("DateTime", () => DateTime.Now)
.Token((token, v) => v.ToString(token));
This opens the door for pretty much any custom dynamic tokens you may dream of. We can now rewrite our original example of a URL pattern like so:
/{Content.Container.Owner}/Blog/{Content.Date.yyyy/MM/dd}/{Content.Slug}
You may be wondering what characters are legal in token names. How would you include a dot or curly braces? Well, just double them.
To conclude this post, I'd like to show one more non-trivial example of token implementation. Here is how we'll surface tokens for content item fields:
if (context.Target == "Content") { var forContent = context.For<IContent>("Content"); foreach (var typePart in
forContent.Data.ContentItem.TypeDefinition.Parts) {
foreach (var partField in typePart.PartDefinition.Fields) { var tokenName = "Fields." + typePart.PartDefinition.Name
+ "." + partField.Name; forContent.Token( tokenName, content => LookupField(content,
typePart.PartDefinition.Name,
partField.Name)
.Storage.Get<string>()); forContent.Chain( tokenName, partField.FieldDefinition.Name, content => LookupField(content,
typePart.PartDefinition.Name,
partField.Name)); } } }
With this provider, you can access fields with tokens looking like {Content.Fields.MyPart.MyField}. If the field itself implements its own tokens, you could do crazy things like {Content.Fields.MyPart.MyField.Some.Crazy.Thing}.
That's it for tokens. Next post, I'll show the preliminary design of Autoroutes, which enables the specification of custom URL patterns such as the one I've used as an example in this post.