Tokens - How To Access Custom "Fields" In Tokens?

Topics: Customizing Orchard, Writing modules
Oct 28, 2014 at 3:18 PM
I have a custom part, eg ExamplePart / ExamplePartRecord. This has columns set up eg:
SchemaBuilder.CreateTable("ExamplePartRecord", table => table
                .ContentPartRecord()
                .Column<string>("ExampleName")
                // ...
                );
and in ExamplePart
        public string ExampleName
        {
            get { return Retrieve(r => r.ExampleName); }
            set { Store(r => r.ExampleName, value); }
        }
I've seen on this announcement post that we should be able to use custom tokens like this: {Content.Fields.MyPart.MyField}.

However when I set up an autoroute like this:
.WithSetting("AutorouteSettings.PatternDefinitions", "[{Name:'Profile Page Url', Pattern: 'profiles/{Content.Fields.ExamplePart.ExampleName}', Description: '/profiles/(profilename)'}]")
It doesn't insert anything in at the token {} placeholder.

Putting a breakpoint on Evaluate() in \src\Orchard.Web\Modules\Orchard.Tokens\Providers\ContentTokens.cs. is showing that the custom part doesn't contain any "fields".

What do I have to do to add the (properties / fields / columns / don't know what orchard is calling them) on my custom part so that the token code picks up on them?


Where can I see the available tokens?
I can see in the token code that it appears to be injecting descriptions for all the tokens that are created but I can't find any runtime admin UI that will show me what the complete selection of available tokens are? Where are these descriptions surfaced?
Oct 28, 2014 at 3:57 PM
rtpHarry wrote:
Where can I see the available tokens?
I can see in the token code that it appears to be injecting descriptions for all the tokens that are created but I can't find any runtime admin UI that will show me what the complete selection of available tokens are? Where are these descriptions surfaced?
I have found the solution to this part, if I attempt to edit a custom route through the admin panel there is a little button which displays all of the available tokens.

Now I just need to answer the first bit.
Oct 28, 2014 at 4:39 PM
Edited Oct 28, 2014 at 4:44 PM
I think there is no out of the box solution. You need to write your own TokenProvider (sounds harder than it is).

Normally you create a folder named "Provider" and add a Class. This class has implement the "Orchard.Tokens.ITokenProvider".

It has two methods - one for describing the tokens (that's what you see on the ui) and one for implementing them.
       // Describes the Tokens
       public void Describe(DescribeContext context)
        {
            context.For("Content", T("Content"), T("Content Type"))
                .Token("ExamplePart", T("ExamplePart"), T("Gets the ExamplePart"));
 
            context.For("ExamplePart", T("ExamplePart"), T("Example Part"))
                .Token("ExampleName", T("ExampleName"), T("Gets the ExampleName"));   
        }

        // Implements the tokens
        public void Evaluate(EvaluateContext context)
        {
            // Tokens for IContent (means for all ContentItems) - works if a ExamplePart is attached - otherwise the token is empty
            context.For<IContent>("Content")
                .Token("ExamplePart", content=> content.As<ExamplePart>() != null ? content.As<UserPart>().ExampleName : null)
                // Chains the Token - that means you can now use something like that {Content.ExamplePart.Foo} or {Content.ExamplePart.ExampleName}
                .Chain("ExamplePart", "ExamplePart", content => content.As<ExamplePart>() ?? null);

            // Tokens for the ExamplePart (used for the Chaining)
            context.For<ExamplePart>("ExamplePart")
                .Token("ExampleName", part=> part.ExampleName)
                .Token("Foo", part => part.Foo);

        }
Code is not tested, but should hopefully work :). If you have any questions feel free to ask ;)

edit: Maybe just for explanation: Your Method {Content.Fields.ExamplePart.ExampleName} does not work because a Part is not a Field. If you add a Field to your ContentType with the Name "ExampleName" you are able to use the Token {Content.Fields.ExamplePart.ExampleName}. This is because for fields it uses a generic pattern to add them in a token provider. If you want your approach you can write your own TokenProvider which uses Reflection and goes through all parts and there properties - but it seems like a security problem to me, if you can access all part properties.
Oct 28, 2014 at 4:48 PM
Thanks for your reply. I had seen some stuff about making custom tokens and this looks similar to what I'd read.

I thought maybe there would be some [ ] attribute I could decorate the property with so that it was recognised as a field or something like that.

I will make a TokenProvider class if not.
Oct 28, 2014 at 9:15 PM
Edited Oct 28, 2014 at 9:20 PM
No there is no such attribute (or only I don't know it :) ). What you can do is, that you change your migration and instead of adding a property, add a TextField.
 ContentDefinitionManager.AlterPartDefinition("ExamplePart", builder =>
 builder.WithField("ExampleText", field => field
                .OfType("TextField")
                .WithDisplayName("Example Text")
            );
Now your Part is using a Field and it should be possible to access it with {Content.Field.ExamplePart.ExampleText}.