文章 注册模板令牌

令牌通过充当占位符来分隔数据和表示形式,每个占位符表示模板引擎在模板中用于嵌入正确对象属性数据并渲染的一个位置。从本质上讲,每个令牌都是一种向 UI 公开数据对象属性以便在模板中进行操作的方法,前提是基础信息将根据所发生操作的上下文而更改。

为什么/何时应该使用令牌

如果要创建新的应用程序或内容,或者向现有内容或应用程序添加其他数据/属性,则应考虑添加标记,以便可以在模板中使用您的数据。

注册令牌

ITokenRegistrar 插件用于识别在模板 UI 中使用的数据片段或属性。在实现 ITokenRegistrar 之前,我们需要设置一些东西。首先是我们可以为其创建令牌的示例数据类。这将对应于您为与 Limyee 电商平台集成而创建的自定义内容或其他对象。请记住,这是一个说明示例,不是有关生成数据类的指南。

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public DateTime Birthday { get; set; }

    public List<BlogPost> AuthoredPosts { get; set; }
    public BlogPost FavoritePost { get; set; }

    public bool IsBirthdayToday()
    {
        return DateTime.Now.Date.Equals(Birthday.Date);
    }
}

另一个关键组件是实现令牌类接口。我们将接口定义为可扩展性工具集的一部分,但将实现留给第三方开发人员,以允许他们灵活地处理翻译等组件。对于我们的示例,我们将使用 ITokenizedTemplatePrimitiveToken 的简化实现,该实现不包括翻译。本文末尾有包含翻译的每种令牌类型的完整实现。

public class SampleBasicToken : ITokenizedTemplatePrimitiveToken
{
    public string Name { get; private set; }
    public string Description { get; private set; }
    public Guid Id { get; private set; }
    public Guid? DataTypeId { get; private set; }
    public PrimitiveType ValueType { get; private set; }
    private readonly Func<TemplateContext, object> _resolve;
    public object Resolve(TemplateContext context)
    {
        return _resolve(context);
    }

    private readonly string _preview;
    public string Preview()
    {
        return _preview;
    }

    public SampleBasicToken(string name, string description, 
        Guid id, Guid? dataTypeId, PrimitiveType valueType, 
        Func<TemplateContext, object> resolveFunction, string preview = null)
    {
        Name = name;
        Description = description;
        Id = id;
        DataTypeId = dataTypeId;
        ValueType = valueType;
        _resolve = resolveFunction;

        _preview = preview ?? Name;
    }
}

定义令牌后,我们现在可以注册令牌的实例,这些实例将在有相应的上下文时显示在模板编辑器中。注册令牌就像对 Register 的一系列调用一样简单。在声明中,定义了一个 Func<TemplateContext, object>(),负责从提供的上下文中提取它需要提供的数据。

// Generate a custom Guid to represent this data type
public static readonly Guid DataTypeId = new Guid("CDD8734D-8938-4E9F-BDEB-F3CA99C19453");

public void RegisterTokens(ITokenizedTemplateTokenController tokenController)
{
    // Naming convention allows consistency in display in the token drop-down menu.
    tokenController.Register(new SampleBasicToken("Person: Name",
        "The name of the person.",
        // A unique, static id is needed for each token
        Guid.Parse("1CB699C3-61DD-490F-9BEF-0284A1FD3445"),
        DataTypeId,
        PrimitiveType.String,
        context => context.Get<Person>(_dataTypeId).Name));

    tokenController.Register(new SampleBasicToken("Person: Age",
        "The age of the person.",
        Guid.Parse("DF6AF0E3-9F79-40FC-8C39-0B838AB5B14A"),
        DataTypeId,
        PrimitiveType.Int,
        context => context.Get<Person>(_dataTypeId).Age));

    tokenController.Register(new SampleBasicToken("Person: Birthday",
        "The person's birthday.",
        Guid.Parse("338C1BC7-55AD-4EA0-B29B-8DA1BF432D83"),
        DataTypeId,
        PrimitiveType.DateTime,
        context => context.Get<Person>(_dataTypeId).Birthday));

    tokenController.Register(new SampleBasicToken("Person: Is Birthday Today",
        "True if today is the person's birthday.",
        Guid.Parse("8CBB6B81-CA9D-494F-86CA-5C8362FE356B"),
        DataTypeId,
        PrimitiveType.Bool,
        context => context.Get<Person>(_dataTypeId).IsBirthdayToday()));
}

有关 ContentTypeId 与 DataTypeId 的注意事项:每个令牌类别都需要一个唯一的 ID 来表示何时将这些令牌包含在模板编辑器的令牌选择列表中。由于其中一些标记用于内容类型(如博客文章),因此在这些情况下重用了 ContentTypeId。对于不属于内容类型的令牌(例如,点赞或提及),将创建一个新 Id 并将其称为 DataTypeId。

将您的令牌链接到相关数据类型的令牌

ITokenizedTemplatePrimitiveToken 只是一种类型的令牌,是最简单的类型,只是简单地呈现一段数据。ITokenizedTemplateLinkUrlToken 和 ITokenizedTemplateImageUrlToken 是基本令牌的变体,在提供的数据周围自动渲染<a>或 <img> 标签。另外两种令牌类型,ITokenizedTemplateEnumerableToken 和 ITokenizedTemplateDataTypeContainerToken,主要用于公开类中的对象属性,而这些对象属性又具有由令牌公开的本身属性。这些实现在解析时需要对 TemplateContext 进行一些操作,但允许访问同一模板中所有可能相关对象的所有可能属性。

ITokenizedTemplateDataTypeContainerToken 需要将对象属性添加到上下文中,由该对象令牌的唯一 ID 标识。

public class SampleDataTypeContainerToken : ITokenizedTemplateDataTypeContainerToken
{
    public string Name { get; private set; }
    public string Description { get; private set; }
    public Guid Id { get; private set; }
    public Guid? DataTypeId { get; private set; }
    public Guid[] ContextualDataTypeIds { get; private set; }
    private readonly Action<TemplateContext> _resolve;
    public void Resolve(TemplateContext context)
    {
        _resolve(context);
    }

    public SampleDataTypeContainerToken(string name, string description,
        Guid id, Guid? dataTypeId, Guid[] contextualDataTypeIds, 
        Action<TemplateContext> resolveFunction)
    {
        Name = name;
        Description = description;
        Id = id;
        DataTypeId = dataTypeId;
        ContextualDataTypeIds = contextualDataTypeIds;
        _resolve = resolveFunction;
    }
}

注册令牌:

tokenController.Register(new SampleDataTypeContainerToken("Person: Favorite Post",
    "The person's favorite blog post.",
    Guid.Parse("0EB53702-F4B4-47B8-93FD-8DE914DC9B25"),
    DataTypeId,
    new[] { PublicApi.Users.ContentTypeId },
    context => context.AddItem(PublicApi.BlogPosts.ContentTypeId, 
                    context.Get<Person>(_dataTypeId).FavoritePost)));

ITokenizedTemplateEnumerableToken 用于列表属性,并要求您创建一个子上下文列表,每个子上下文都填充了列表中的一个对象。

public class SampleEnumerableToken : ITokenizedTemplateEnumerableToken
{
    public string Name { get; private set; }
    public string Description { get; private set; }
    public Guid Id { get; private set; }
    public Guid? DataTypeId { get; private set; }
    public Guid[] ContextualDataTypeIds { get; private set; }
    private readonly Func<TemplateContext, IEnumerable<TemplateContext>> _resolve;
    public IEnumerable<TemplateContext> Resolve(TemplateContext context)
    {
        return _resolve(context);
    }

    public SampleEnumerableToken(string name, string description, Guid id,
        Guid? dataTypeId, Guid[] contextualDataTypeIds, 
        Func<TemplateContext, IEnumerable<TemplateContext>> resolveFunction)
    {
        Name = name;
        Description = description;
        Id = id;
        DataTypeId = dataTypeId;
        ContextualDataTypeIds = contextualDataTypeIds;
        _resolve = resolveFunction;
    }
}

注册令牌:

tokenController.Register(new SampleEnumerableToken("Person: Authored Posts List",
    "List of blog posts this person has created.",
    Guid.Parse("FE57E5B8-6D9D-4714-8902-B49776B4980C"),
    DataTypeId,
    new[] { PublicApi.Users.ContentTypeId },
    context => context.Get<Person>(_dataTypeId).AuthoredPosts.Select(post =>
    {
        var itemContext = new TemplateContext();
        itemContext.AddItem(PublicApi.BlogPosts.ContentTypeId, post);
        return itemContext;
    })));

查看结果

当在其 ContextualDataTypeIds 中包含 Person 数据类型 ID 的插件编辑模板时,您将看到可用于插入到模中的令牌列表:

在模板中使用令牌

您可以设置插件以提供模板,并包含令牌或内置令牌。有关详细信息,请参阅有关基于模板的电子邮件的文章。

令牌实现

using System;
using System.Collections.Generic;
using Limyee.Extensibility.Api.Entities.Version1;
using Limyee.Extensibility.Templating.Version1;
using Limyee.Extensibility.Version1;

namespace Limyee.Samples
{
    internal class TokenizedTemplateToken : ITokenizedTemplatePrimitiveToken
    {
        private readonly Func<string> _name;
        public string Name { get { return _name(); } }
        private readonly Func<string> _description;
        public string Description { get { return _description(); } }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public PrimitiveType ValueType { get; private set; }
        private readonly Func<TemplateContext, object> _resolve;
        public object Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        private readonly Func<string> _preview;
        public string Preview()
        {
            return _preview();
        }

        public TokenizedTemplateToken(string name, string description, ITranslatablePluginController controller, Guid id, Guid? dataTypeId, Func<TemplateContext, object> resolveFunction, string preview = null)
            : this(name, description, controller, id, dataTypeId, PrimitiveType.String, resolveFunction, preview)
        { }

        public TokenizedTemplateToken(string name, string description, ITranslatablePluginController controller, Guid id, Guid? dataTypeId, PrimitiveType valueType, Func<TemplateContext, object> resolveFunction, string preview = null)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            ValueType = valueType;
            _resolve = resolveFunction;

            _preview = preview != null ? () => controller.GetLanguageResourceValue(preview) : _name;
        }
    }

    internal class TokenizedTemplateImageUrlToken : ITokenizedTemplateImageUrlToken
    {
        private readonly Func<string> _name;
        public string Name { get { return _name(); } }
        private readonly Func<string> _description;
        public string Description { get { return _description(); } }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        private readonly Func<TemplateContext, string> _resolve;
        public string Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        private readonly Func<string> _preview;
        public string Preview()
        {
            return _preview();
        }

        public TokenizedTemplateImageUrlToken(string name, string description, ITranslatablePluginController controller, Guid id, Guid? dataTypeId, Func<TemplateContext, string> resolveFunction, string preview = null)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            _resolve = resolveFunction;

            _preview = preview != null ? () => controller.GetLanguageResourceValue(preview) : _name;
        }

        public TokenizedTemplateImageUrlToken(string name, string description, ITranslatablePluginController controller, Guid id, Guid? dataTypeId, Func<TemplateContext, string> resolveFunction, Func<string> preview)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            _resolve = resolveFunction;

            _preview = preview ?? _name;
        }
    }

    internal class TokenizedTemplateLinkUrlToken : ITokenizedTemplateLinkUrlToken
    {
        private readonly Func<string> _name;
        public string Name { get { return _name(); } }
        private readonly Func<string> _description;
        public string Description { get { return _description(); } }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        private readonly Func<TemplateContext, string> _resolve;
        public string Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        private readonly Func<string> _preview;
        public string Preview()
        {
            return _preview();
        }

        public TokenizedTemplateLinkUrlToken(string name, string description, ITranslatablePluginController controller, Guid id, Guid? dataTypeId, Func<TemplateContext, string> resolveFunction, string preview = null)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            _resolve = resolveFunction;
            _preview = preview != null ? () => controller.GetLanguageResourceValue(preview) : _name;
        }
    }

    internal class TokenizedTemplateEnumerableToken : ITokenizedTemplateEnumerableToken
    {
        private readonly Func<string> _name;
        public string Name { get { return _name(); } }
        private readonly Func<string> _description;
        public string Description { get { return _description(); } }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public Guid[] ContextualDataTypeIds { get; private set; }
        private readonly Func<TemplateContext, IEnumerable<TemplateContext>> _resolve;
        public IEnumerable<TemplateContext> Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        public TokenizedTemplateEnumerableToken(string name, string description, ITranslatablePluginController controller, Guid id,
            Guid? dataTypeId, Guid[] contextualDataTypeIds, Func<TemplateContext, IEnumerable<TemplateContext>> resolveFunction)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            ContextualDataTypeIds = contextualDataTypeIds;
            _resolve = resolveFunction;
        }
    }

    internal class TokenizedTemplateDataTypeContainerToken : ITokenizedTemplateDataTypeContainerToken
    {
        private readonly Func<string> _name;
        public string Name { get { return _name(); } }
        private readonly Func<string> _description;
        public string Description { get { return _description(); } }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public Guid[] ContextualDataTypeIds { get; private set; }
        private readonly Action<TemplateContext> _resolve;
        public void Resolve(TemplateContext context)
        {
            _resolve(context);
        }

        public TokenizedTemplateDataTypeContainerToken(string name, string description, ITranslatablePluginController controller,
            Guid id, Guid? dataTypeId, Guid[] contextualDataTypeIds, Action<TemplateContext> resolveFunction)
        {
            _name = () => controller.GetLanguageResourceValue(name);
            _description = () => controller.GetLanguageResourceValue(description);
            Id = id;
            DataTypeId = dataTypeId;
            ContextualDataTypeIds = contextualDataTypeIds;
            _resolve = resolveFunction;
        }
    }
}

带有翻译的完整令牌注册器示例

using System;
using System.Collections.Generic;
using System.Linq;
using Limyee.Components.TokenizedTemplates;
using Limyee.Extensibility;
using Limyee.Extensibility.Api.Entities.Version1;
using Limyee.Extensibility.Api.Version1;
using Limyee.Extensibility.Templating.Version1;
using Limyee.Extensibility.Version1;

namespace Limyee.Samples
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public DateTime Birthday { get; set; }

        public List<BlogPost> AuthoredPosts { get; set; }
        public BlogPost FavoritePost { get; set; }

        public bool IsBirthdayToday()
        {
            return DateTime.Now.Date.Equals(Birthday.Date);
        }
    }

    public class SampleBasicToken : ITokenizedTemplatePrimitiveToken
    {
        public string Name { get; private set; }
        public string Description { get; private set; }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public PrimitiveType ValueType { get; private set; }
        private readonly Func<TemplateContext, object> _resolve;
        public object Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        private readonly string _preview;
        public string Preview()
        {
            return _preview;
        }

        public SampleBasicToken(string name, string description,
            Guid id, Guid? dataTypeId, PrimitiveType valueType,
            Func<TemplateContext, object> resolveFunction, string preview = null)
        {
            Name = name;
            Description = description;
            Id = id;
            DataTypeId = dataTypeId;
            ValueType = valueType;
            _resolve = resolveFunction;

            _preview = preview ?? Name;
        }
    }

    public class SampleDataTypeContainerToken : ITokenizedTemplateDataTypeContainerToken
    {
        public string Name { get; private set; }
        public string Description { get; private set; }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public Guid[] ContextualDataTypeIds { get; private set; }
        private readonly Action<TemplateContext> _resolve;
        public void Resolve(TemplateContext context)
        {
            _resolve(context);
        }

        public SampleDataTypeContainerToken(string name, string description,
            Guid id, Guid? dataTypeId, Guid[] contextualDataTypeIds,
            Action<TemplateContext> resolveFunction)
        {
            Name = name;
            Description = description;
            Id = id;
            DataTypeId = dataTypeId;
            ContextualDataTypeIds = contextualDataTypeIds;
            _resolve = resolveFunction;
        }
    }

    public class SampleEnumerableToken : ITokenizedTemplateEnumerableToken
    {
        public string Name { get; private set; }
        public string Description { get; private set; }
        public Guid Id { get; private set; }
        public Guid? DataTypeId { get; private set; }
        public Guid[] ContextualDataTypeIds { get; private set; }
        private readonly Func<TemplateContext, IEnumerable<TemplateContext>> _resolve;
        public IEnumerable<TemplateContext> Resolve(TemplateContext context)
        {
            return _resolve(context);
        }

        public SampleEnumerableToken(string name, string description, Guid id,
            Guid? dataTypeId, Guid[] contextualDataTypeIds,
            Func<TemplateContext, IEnumerable<TemplateContext>> resolveFunction)
        {
            Name = name;
            Description = description;
            Id = id;
            DataTypeId = dataTypeId;
            ContextualDataTypeIds = contextualDataTypeIds;
            _resolve = resolveFunction;
        }
    }

    public class PersonTokens : ITokenRegistrar, ITranslatablePlugin, IEmailTemplatePlugin
    {
        #region IPlugin
        public string Name
        {
            get { return "人员令牌"; }
        }

        public string Description
        {
            get { return "在模板中为人员数据注册令牌。"; }
        }

        public void Initialize() { }
        #endregion

        // Generate a custom Guid to represent
        private readonly Guid _dataTypeId = new Guid("CDD8734D-8938-4E9F-BDEB-F3CA99C19453");

        public void RegisterTokens(ITokenizedTemplateTokenController tokenController)
        {
            // Naming convention allows consistency in display in the token drop-down menu.
            tokenController.Register(new TokenizedTemplateToken("PersonName",
                "PersonName_Description", _controller,
                // A unique, static id is needed for each token
                Guid.Parse("1CB699C3-61DD-490F-9BEF-0284A1FD3445"),
                _dataTypeId,
                PrimitiveType.String,
                context => context.Get<Person>(_dataTypeId).Name));

            tokenController.Register(new TokenizedTemplateToken("PersonAge",
                "PersonAge_Description", _controller,
                Guid.Parse("DF6AF0E3-9F79-40FC-8C39-0B838AB5B14A"),
                _dataTypeId,
                PrimitiveType.Int,
                context => context.Get<Person>(_dataTypeId).Age));

            tokenController.Register(new TokenizedTemplateToken("PersonBirthday",
                "PersonBirthday_Description", _controller,
                Guid.Parse("338C1BC7-55AD-4EA0-B29B-8DA1BF432D83"),
                _dataTypeId,
                PrimitiveType.DateTime,
                context => context.Get<Person>(_dataTypeId).Birthday));

            tokenController.Register(new TokenizedTemplateToken("PersonIsBirthdayToday",
                "PersonIsBirthdayToday_Description", _controller,
                Guid.Parse("8CBB6B81-CA9D-494F-86CA-5C8362FE356B"),
                _dataTypeId,
                PrimitiveType.Bool,
                context => context.Get<Person>(_dataTypeId).IsBirthdayToday()));

            tokenController.Register(new TokenizedTemplateDataTypeContainerToken("PersonFavoritePost",
                "PersonFavoritePost_Description", _controller,
                Guid.Parse("0EB53702-F4B4-47B8-93FD-8DE914DC9B25"),
                _dataTypeId,
                new[] { Apis.Get<IUsers>().ContentTypeId },
                context => context.AddItem(Apis.Get<IBlogPosts>().ContentTypeId,
                                context.Get<Person>(_dataTypeId).FavoritePost)));

            tokenController.Register(new TokenizedTemplateEnumerableToken("PersonAuthoredPosts",
                "PersonAuthoredPosts_Description", _controller,
                Guid.Parse("FE57E5B8-6D9D-4714-8902-B49776B4980C"),
                _dataTypeId,
                new[] { Apis.Get<IUsers>().ContentTypeId },
                context => context.Get<Person>(_dataTypeId).AuthoredPosts.Select(post =>
                {
                    var itemContext = new TemplateContext();
                    itemContext.AddItem(Apis.Get<IBlogPosts>().ContentTypeId, post);
                    return itemContext;
                })));
        }

        #region ITranslatablePlugin
        private Translation[] _defaultTranslations;
        public Translation[] DefaultTranslations
        {
            get
            {
                if (_defaultTranslations == null)
                {
                    var enUs = new Translation("zh-cn");
                    enUs.Set("PersonName", "个人:名称");
                    enUs.Set("PersonName_Description", "个人的名称。");
                    enUs.Set("PersonAge", "个人:年龄");
                    enUs.Set("PersonAge_Description", "个人的年龄。");
                    enUs.Set("PersonBirthday", "个人:生日");
                    enUs.Set("PersonBirthday_Description", "个人的生日。");
                    enUs.Set("PersonIsBirthdayToday", "个人:今天是否生日");
                    enUs.Set("PersonIsBirthdayToday_Description", "如果今天是此人的生日,是 True。");
                    enUs.Set("PersonFavoritePost", "个人::收藏文章");
                    enUs.Set("PersonFavoritePost_Description", "此人的收藏博客文章");
                    enUs.Set("PersonAuthoredPosts", "个人:创作的文章列表");
                    enUs.Set("PersonAuthoredPosts_Description", "此人创建的博客文章列表。");

                    _defaultTranslations = new[] { enUs };
                }
                return _defaultTranslations;
            }
        }

        private ITranslatablePluginController _controller;
        public void SetController(ITranslatablePluginController controller)
        {
            _controller = controller;
        }
        #endregion

        private ITemplatablePluginController _templatablePluginController;
        public void SetController(ITemplatablePluginController controller)
        {
            _templatablePluginController = controller;
        }

        private TokenizedTemplate[] _defaultTemplates;
        public TokenizedTemplate[] DefaultTemplates
        {
            get
            {
                if (_defaultTemplates == null)
                {
                    var myTemplate = new TokenizedTemplate(EmailTemplateConstants.Body)
                    {
                        Name = "Template Body",
                        Description = "Sample template",
                        ContextualDataTypeIds = new[]
                        {
                            _dataTypeId
                        }
                    };
                    myTemplate.Set("en-us", @"");

                    _defaultTemplates = new[] { myTemplate };
                }
                return _defaultTemplates;
            }
        }
    }
}