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

发布于 2024-07-16 14:34:13 字数 303 浏览 7 评论 0原文

我需要向用户发送电子邮件通知,并且需要允许管理员提供消息正文(也可能还有标题)的模板。

我想要像 string.Format 这样的东西,它允许我提供命名的替换字符串,因此模板可以如下所示:

Dear {User},

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

Regards,

-- 
{Signature}

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

I need to send email notifications to users and I need to allow the admin to provide a template for the message body (and possibly headers, too).

I'd like something like string.Format that allows me to give named replacement strings, so the template can look like this:

Dear {User},

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

Regards,

-- 
{Signature}

What's the simplest way for me to do that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

画尸师 2024-07-23 14:34:13

对于那些可以使用新版本 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}."

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

Here is the version for those of you who can use a new version of 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}."

In a line - this is now a fully supported language feature (string interpolation).

人│生佛魔见 2024-07-23 14:34:13

您可以使用“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);

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

You can use the "string.Format" method:

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);

It allows you to change the content in the future and is friendly for localization.

谜兔 2024-07-23 14:34:13

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

例子:

using Antlr.StringTemplate;
using Antlr.StringTemplate.Language;
 
StringTemplate hello = new StringTemplate("Hello, $name$", typeof(DefaultTemplateLexer));
hello.SetAttribute("name", "World");
Console.Out.WriteLine(hello.ToString());

Use a templating engine. StringTemplate is one of those, and there are many.

Example:

using Antlr.StringTemplate;
using Antlr.StringTemplate.Language;
 
StringTemplate hello = new StringTemplate("Hello, $name
quot;, typeof(DefaultTemplateLexer));
hello.SetAttribute("name", "World");
Console.Out.WriteLine(hello.ToString());
递刀给你 2024-07-23 14:34:13

我编写了一个非常简单的库 SmartFormat ,它满足您的所有要求。 它专注于编写“自然语言”文本,非常适合从列表生成数据或应用条件逻辑。

语法与String.Format极其相似,并且非常简单且易于学习和使用。 以下是文档中的语法示例:

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

该库具有出色的错误处理选项(忽略错误、输出错误、引发错误),并且是开源的且易于扩展,因此您还可以使用其他功能来增强它。

I wrote a pretty simple library, SmartFormat which meets all your requirements. It is focused on composing "natural language" text, and is great for generating data from lists, or applying conditional logic.

The syntax is extremely similar to String.Format, and is very simple and easy to learn and use. Here's an example of the syntax from the documentation:

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

The library has great error-handling options (ignore errors, output errors, throw errors) and is open source and easily extensible, so you can also enhance it with additional features too.

朱染 2024-07-23 14:34:13

基于 Benjamin Gruenbaum 的答案,在 C# 版本 6 中,您可以添加一个带有 $ 的 @,并且几乎可以按原样使用您的代码,例如:

var text = $@"Dear {User},

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

Regards,

-- 
{Signature}
";

$ 用于字符串插值: https://learn.microsoft.com/en-us/dotnet/csharp/ language-reference/tokens/interpolated

@ 是逐字标识符:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/verbatim

...您可以使用这些结合起来。

:o)

编辑...

C# 中带有插值和逐字字符串的字符串模板很棒,但如果您想在运行时提供模板,还有另一个选择是这样的:

https://github.com/Handlebars-Net /Handlebars.Net

:o)

Building on Benjamin Gruenbaum's answer, in C# version 6 you can add a @ with the $ and pretty much use your code as it is, e.g.:

var text = $@"Dear {User},

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

Regards,

-- 
{Signature}
";

The $ is for string interpolation: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated

The @ is the verbatim identifier: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/verbatim

...and you can use these in conjunction.

:o)

Edit...

String templates with interpolation and verbatim strings in C# are great but another option if you want to supply the template at runtime is this:

https://github.com/Handlebars-Net/Handlebars.Net

:o)

平生欢 2024-07-23 14:34:13

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

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;
        });
    }
}

使用

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

说明

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

A very simple regex-based solution. Supports \n-style single character escape sequences and {Name}-style named variables.

Source

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;
        });
    }
}

Usage

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

Notes

  • I've only defined \n, \r, \\ and \{ escape sequences and hard-coded them. You could easily add more or make them definable by the consumer.
  • I've made variable names case-insensitive, as things like this are often presented to end-users/non-programmers and I don't personally think that case-sensitivity make sense in that use-case - it's just one more thing they can get wrong and phone you up to complain about (plus in general if you think you need case sensitive symbol names what you really need are better symbol names). To make them case-sensitive, simply remove the RegexOptions.IgnoreCase flag.
  • I strip invalid variable names and escape sequences from the result string. To leave them intact, return Match.Value instead of the empty string at the end of the Regex.Replace callback. You could also throw an exception.
  • I've used {var} syntax, but this may interfere with the native interpolated string syntax. If you want to define templates in string literals in you code, it might be advisable to change the variable delimiters to e.g. %var% (regex \\.|%([a-z0-9_.\-]+)%) or some other syntax of your choosing which is more appropriate to the use case.
何以畏孤独 2024-07-23 14:34:13

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

string myString = template.Replace("FirstName", "John").Replace("LastName", "Smith").Replace("FinishTime", DateTime.Now.ToShortDateString());

或者,如果您需要更强大且有更多选项的东西,您可以使用 Regex.Replace(...)。

阅读这篇有关 codeproject 的文章,查看哪个字符串替换选项最快为你。

You could use string.Replace(...), eventually in a for-each through all the keywords. If there are only a few keywords you can have them on a line like this:

string myString = template.Replace("FirstName", "John").Replace("LastName", "Smith").Replace("FinishTime", DateTime.Now.ToShortDateString());

Or you could use Regex.Replace(...), if you need something a bit more powerful and with more options.

Read this article on codeproject to view which string replacement option is fastest for you.

甜妞爱困 2024-07-23 14:34:13

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

https://github.com/crozone/格式 | 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);
    }


它也允许格式化内联替换。 例如,尝试将 emailTemplate 中的 {FinishTime} 更改为 {FinishTime:HH:mm:ss}

In case someone is searching for an alternative -- an actual .NET one:

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

A nice simple extendable solution. Thank you crozone!

So using the string extension provided in FormatWith here are two examples:

    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", 
quot;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; } = 
quot;Sincerely,{Environment.NewLine}Admin";
    }

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

        System.Console.WriteLine(emailBody);
    }


It allows formatting the replacement inline as well. For example, try changing {FinishTime} to {FinishTime:HH:mm:ss} in emailTemplate.

东京女 2024-07-23 14:34:13

实际上,您可以使用 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>

然后创建一个 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);

之后,应用转换:

        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();

Actually, you can use XSLT.
You create a simple XML template:

<?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>

Then create a XmlDocument to perform transformation against:
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);

After that, apply the transformation:

        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();
初心 2024-07-23 14:34:13

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

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

class JobDetails
{
    public string User 
    { 
        get;
        set; 
    }        
}

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

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;
            }
        }
    }
}

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

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

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

string message = string.Format(new ExampleFormatter(), template, job);

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

Implementing your own custom formatter might be a good idea.

Here's how you do it. First, create a type that defines the stuff you want to inject into your message. Note: I'm only going to illustrate this with the User part of your template...

class JobDetails
{
    public string User 
    { 
        get;
        set; 
    }        
}

Next, implement a simple custom formatter...

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;
            }
        }
    }
}

Finally, use it like this...

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

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

string message = string.Format(new ExampleFormatter(), template, job);

... which will generate the text "Dear Martin Peck. Your job finished...".

缘字诀 2024-07-23 14:34:13

如果您需要非常强大的功能(但实际上不是最简单的方法),您可以托管 ASP.NET 并将其用作模板引擎。

您将拥有 ASP.NET 的所有功能来格式化消息正文。

If you need something very powerful (but really not the simplest way) you can host ASP.NET and use it as your templating engine.

You'll have all the power of ASP.NET to format the body of your message.

我最亲爱的 2024-07-23 14:34:13

如果您使用 VB.NET 进行编码,则可以使用 XML 文字。 如果您使用 C# 进行编码,则可以使用 ShatDevelop 将 VB.NET 中的文件与 C# 代码放在同一项目中。

If you are coding in VB.NET you can use XML literals. If you are coding in C# you can use ShartDevelop to have files in VB.NET in the same project as C# code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文