在.NET中进行字符串模板化的好方法是什么?

Sim*_*mon 47 .net c# string templating

我需要向用户发送电子邮件通知,我需要允许管理员为邮件正文(也可能是标题)提供模板.

我喜欢这样的东西string.Format允许我给出命名的替换字符串,所以模板看起来像这样:

Dear {User},

Your job finished at {FinishTime} and your file is available for download at {FileURL}.

Regards,

-- 
{Signature}
Run Code Online (Sandbox Code Playgroud)

对我来说最简单的方法是什么?

Ben*_*aum 36

以下是可以使用新版C#的人的版本:

// add $ at start to mark string as template
var template = $"Your job finished at {FinishTime} and your file is available for download at {FileURL}."
Run Code Online (Sandbox Code Playgroud)

在一行中 - 这是一个完全支持的语言功能(字符串插值).

  • 请注意,这是一个编译时替换,基本上是围绕`String.Format()`的糖,如果您在运行时获取模板字符串,则无效.但是,在编译时最好的方法是+1. (19认同)
  • @Martijn很遗憾,措辞不好,我的意思是模板本身是在编译时定义的,并且不可能让用户改变模板.编译器有效地生成`string.Format()`替换,虽然可以让用户重新定义它们,但它们不是特别用户友好,因为它们不能再在模板中使用*named*值.当我到达这个问题时,我所追求的就是我最终为我的答案写的解决方案. (4认同)
  • 您可以将代码提取到函数中,然后将这些项作为参数传递 - 这是_as_表达式作为其他模板技术. (2认同)
  • @DaveRandom:[docs](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interpolated-strings) 另有说法:`每次使用内插字符串执行。这允许您将内插字符串的定义和评估分开。` 或者我读错了吗? (2认同)

Ant*_*lev 22

使用模板引擎.StringTemplate就是其中之一,而且有很多.

  • 如果样本页面可以包含任何使用示例,我会很高兴.链接到C#样本页面:https://theantlrguy.atlassian.net/wiki/display/ST4/Using+StringTemplate+with+CSharp (6认同)
  • 上面的 atlassian.net 链接需要登录。请尝试使用此链接来查看 StringTemplate 的示例:https://theantlrguy.atlassian.net/wiki/spaces/ST/pages/1409116/Five+minute+Introduction (2认同)

TcK*_*cKs 17

您可以使用"string.Format"方法:

var user = GetUser();
var finishTime = GetFinishTime();
var fileUrl = GetFileUrl();
var signature = GetSignature();
string msg =
@"Dear {0},

Your job finished at {1} and your file is available for download at {2}.

Regards,

--
{3}";
msg = string.Format(msg, user, finishTime, fileUrl, signature);
Run Code Online (Sandbox Code Playgroud)

它允许您在将来更改内容并且对本地化很友好.

  • 是的,但是如果模板包含未被替换的符号,则任何解决方案都应该产生异常.最好的做法是说明一些事情是错误的,而不是将你的头埋在沙子里! (4认同)

Sco*_*pey 14

SmartFormat是一个非常简单的库,可以满足您的所有要求.它专注于撰写"自然语言"文本,非常适合从列表生成数据或应用条件逻辑.

语法非常相似String.Format,并且非常简单易学易用.以下是文档中语法的示例:

Smart.Format("{Name}'s friends: {Friends:{Name}|, |, and}", user)
// Result: "Scott's friends: Michael, Jim, Pam, and Dwight"
Run Code Online (Sandbox Code Playgroud)

该库具有很好的错误处理选项(忽略错误,输出错误,抛出错误).显然,这对你的例子来说是完美的.

该库是开源的,易于扩展,因此您也可以使用其他功能来增强它.


Ovi*_*Ovi 10

您可以使用string.Replace(...),最终在for-each中使用所有关键字.如果只有几个关键字,您可以将它们放在这样的一行:

string myString = template.Replace("FirstName", "John").Replace("LastName", "Smith").Replace("FinishTime", DateTime.Now.ToShortDateString());
Run Code Online (Sandbox Code Playgroud)

或者您可以使用Regex.Replace(...),如果您需要更强大的功能和更多选项.

阅读有关codeproject的这篇文章,以查看最快的字符串替换选项.

  • 如果你有很多参数要替换和/或有很长的字符串,这不是一个好方法——因为字符串是不可变的,所以内存使用量很大。 (2认同)

mrr*_*rrk 9

基于Benjamin Gruenbaum的回答,在C#版本6中,您可以使用$添加@并且几乎可以使用您的代码,例如:

var text = $@"Dear {User},

Your job finished at {FinishTime} and your file is available for download at {FileURL}.

Regards,

-- 
{Signature}
";
Run Code Online (Sandbox Code Playgroud)

  • 字符串插值仅在编译期间起作用,因此您无法从配置文件或数据库等外部源获取字符串模板。 (3认同)

Dav*_*dom 7

一个非常简单的基于正则表达式的解决方案。支持\n-style 单字符转义序列和{Name}-style 命名变量。

来源

class Template
{
    /// <summary>Map of replacements for characters prefixed with a backward slash</summary>
    private static readonly Dictionary<char, string> EscapeChars
        = new Dictionary<char, string>
        {
            ['r'] = "\r",
            ['n'] = "\n",
            ['\\'] = "\\",
            ['{'] = "{",
        };

    /// <summary>Pre-compiled regular expression used during the rendering process</summary>
    private static readonly Regex RenderExpr = new Regex(@"\\.|{([a-z0-9_.\-]+)}",
        RegexOptions.IgnoreCase | RegexOptions.Compiled);

    /// <summary>Template string associated with the instance</summary>
    public string TemplateString { get; }

    /// <summary>Create a new instance with the specified template string</summary>
    /// <param name="TemplateString">Template string associated with the instance</param>
    public Template(string TemplateString)
    {
        if (TemplateString == null) {
            throw new ArgumentNullException(nameof(TemplateString));
        }

        this.TemplateString = TemplateString;
    }

    /// <summary>Render the template using the supplied variable values</summary>
    /// <param name="Variables">Variables that can be substituted in the template string</param>
    /// <returns>The rendered template string</returns>
    public string Render(Dictionary<string, object> Variables)
    {
        return Render(this.TemplateString, Variables);
    }

    /// <summary>Render the supplied template string using the supplied variable values</summary>
    /// <param name="TemplateString">The template string to render</param>
    /// <param name="Variables">Variables that can be substituted in the template string</param>
    /// <returns>The rendered template string</returns>
    public static string Render(string TemplateString, Dictionary<string, object> Variables)
    {
        if (TemplateString == null) {
            throw new ArgumentNullException(nameof(TemplateString));
        }

        return RenderExpr.Replace(TemplateString, Match => {
            switch (Match.Value[0]) {
                case '\\':
                    if (EscapeChars.ContainsKey(Match.Value[1])) {
                        return EscapeChars[Match.Value[1]];
                    }
                    break;

                case '{':
                    if (Variables.ContainsKey(Match.Groups[1].Value)) {
                        return Variables[Match.Groups[1].Value].ToString();
                    }
                    break;
            }

            return string.Empty;
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

用法

var tplStr1 = @"Hello {Name},\nNice to meet you!";
var tplStr2 = @"This {Type} \{contains} \\ some things \\n that shouldn't be rendered";
var variableValues = new Dictionary<string, object>
{
    ["Name"] = "Bob",
    ["Type"] = "string",
};

Console.Write(Template.Render(tplStr1, variableValues));
// Hello Bob,
// Nice to meet you!

var template = new Template(tplStr2);
Console.Write(template.Render(variableValues));
// This string {contains} \ some things \n that shouldn't be rendered
Run Code Online (Sandbox Code Playgroud)

笔记

  • 我只定义\n\r\\\{转义序列和硬编码他们。您可以轻松添加更多内容或让消费者可以定义它们。
  • 我已经使变量名不区分大小写,因为这样的事情经常呈现给最终用户/非程序员,我个人不认为区分大小写在那个用例中有意义 - 这只是他们的另一件事可能会出错并打电话给您抱怨(通常,如果您认为需要区分大小写的符号名称,那么您真正需要的是更好的符号名称)。要使它们区分大小写,只需删除RegexOptions.IgnoreCase标志。
  • 我从结果字符串中去除无效的变量名和转义序列。要保持它们不变,请Match.ValueRegex.Replace回调结束时返回而不是空字符串。您也可以抛出异常。
  • 我使用过{var}语法,但这可能会干扰本机内插字符串语法。如果您想在代码中以字符串文字定义模板,建议将变量分隔符更改为 eg %var%(regex \\.|%([a-z0-9_.\-]+)%) 或您选择的更适合用例的其他一些语法。


jim*_*key 6

如果有人正在寻找替代方案——真正的 .NET 方案:

https://github.com/crozone/FormatWith | https://www.nuget.org/packages/FormatWith

一个很好的简单的可扩展解决方案。谢谢克罗佐内!

因此,使用 FormatWith 中提供的字符串扩展这里有两个示例:

    static string emailTemplate = @"
Dear {User},

Your job finished at {FinishTime} and your file is available for download at {FileURL}.

-- 
{Signature}
    ";

//////////////////////////////////
/// 1. Use a dictionary that has the tokens as keys with values for the replacement
//////////////////////////////////
    public void TestUsingDictionary()
    {    
        var emailDictionary = new Dictionary<string, object>()
        {
            { "User", "Simon" },
            { "FinishTime", DateTime.Now },
            { "FileUrl", new Uri("http://example.com/dictionary") },
            { "Signature", $"Sincerely,{Environment.NewLine}Admin" }
        };

        var emailBody = emailTemplate.FormatWith(emailDictionary);

        System.Console.WriteLine(emailBody);
    }

//////////////////////////////////
/// 2. Use a poco with properties that match the replacement tokens
//////////////////////////////////
    public class MessageValues
    {
        public string User { get; set; } = "Simon";
        public DateTime FinishTime { get; set; } = DateTime.Now;
        public Uri FileURL { get; set; } = new Uri("http://example.com");
        public string Signature { get; set; } = $"Sincerely,{Environment.NewLine}Admin";
    }

    public void TestUsingPoco()
    {
        var emailBody = emailTemplate.FormatWith(new MessageValues());

        System.Console.WriteLine(emailBody);
    }


Run Code Online (Sandbox Code Playgroud)

它还允许格式化内联替换。例如,尝试更改{FinishTime}{FinishTime:HH:mm:ss}in emailTemplate


010*_*101 5

实际上,您可以使用XSLT。您创建一个简单的XML模板:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:template match="TETT">
    <p>
       Dear <xsl:variable name="USERNAME" select="XML_PATH" />,

       Your job finished at <xsl:variable name="FINISH_TIME" select="XML_PATH" /> and your file is available for download at <xsl:variable name="FILE_URL" select="XML_PATH" />.

       Regards,
        -- 
       <xsl:variable name="SIGNATURE" select="XML_PATH" />
    </p>
</xsl:template>
Run Code Online (Sandbox Code Playgroud)

然后创建一个XmlDocument对以下对象执行转换:XmlDocument xmlDoc = new XmlDocument();

        XmlNode xmlNode = xmlDoc .CreateNode(XmlNodeType.Element, "EMAIL", null);
        XmlElement xmlElement= xmlDoc.CreateElement("USERNAME");
        xmlElement.InnerXml = username;
        xmlNode .AppendChild(xmlElement); ///repeat the same thing for all the required fields

        xmlDoc.AppendChild(xmlNode);
Run Code Online (Sandbox Code Playgroud)

之后,应用转换:

        XPathNavigator xPathNavigator = xmlDocument.DocumentElement.CreateNavigator();
        StringBuilder sb = new StringBuilder();
        StringWriter sw = new StringWriter(sb);
        XmlTextWriter xmlWriter = new XmlTextWriter(sw);
        your_xslt_transformation.Transform(xPathNavigator, null, xmlWriter);
        return sb.ToString();
Run Code Online (Sandbox Code Playgroud)

  • 就像我喜欢XSLT一样,这可能使OP中的最后一条语句失败:“我最简单的方法是什么?” :) (18认同)
  • “您创建一个*简单* XML模板”?!什么?!哪有这回事!!! (12认同)
  • 抵押驱动软件开发研究所的程序员同事 https://www.youtube.com/watch?v=7RJmoCWx4cE (2认同)

Mar*_*eck 5

实现您自己的自定义格式化程序可能是一个好主意。

这是你如何做到的。首先,创建一个类型来定义要注入到消息中的内容。注意:我只会用模板的用户部分来说明这一点......

class JobDetails
{
    public string User 
    { 
        get;
        set; 
    }        
}
Run Code Online (Sandbox Code Playgroud)

接下来,实现一个简单的自定义格式化程序...

class ExampleFormatter : IFormatProvider, ICustomFormatter
{
    public object GetFormat(Type formatType)
    {
        return this;
    }

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        // make this more robust
        JobDetails job = (JobDetails)arg;

        switch (format)
        {
            case "User":
            {
                return job.User;
            }
            default:
            {
                // this should be replaced with logic to cover the other formats you need
                return String.Empty;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,像这样使用它......

string template = "Dear {0:User}. Your job finished...";

JobDetails job = new JobDetails()
                     {
                             User = "Martin Peck"
                     };

string message = string.Format(new ExampleFormatter(), template, job);
Run Code Online (Sandbox Code Playgroud)

...这将生成文本“亲爱的马丁派克。你的工作完成了......”。