我需要向用户发送电子邮件通知,并且我需要允许管理员为消息正文(可能还有标题)提供模板。
我想要这样的东西string.Format
允许我给出命名的替换字符串,所以模板看起来像这样:
Dear {User},
Your job finished at {FinishTime} and your file is available for download at {FileURL}.
Regards,
--
{Signature}
对我来说最简单的方法是什么?
我需要向用户发送电子邮件通知,并且我需要允许管理员为消息正文(可能还有标题)提供模板。
我想要这样的东西string.Format
允许我给出命名的替换字符串,所以模板看起来像这样:
Dear {User},
Your job finished at {FinishTime} and your file is available for download at {FileURL}.
Regards,
--
{Signature}
对我来说最简单的方法是什么?
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).
使用模板引擎。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());
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.
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). Obviously, this would work perfect for your example.
The library is open source and easily extensible, so you can also enhance it with additional features too.
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://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated
The @
is the verbatim identifier: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/verbatim
...and you can use these in conjunction.
:o)
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.
A very simple regex-based solution. Supports \n
-style single character escape sequences and {Name}
-style named variables.
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
, \\
and \{
escape sequences and hard-coded them. You could easily add more or make them definable by the consumer.RegexOptions.IgnoreCase
flag.Match.Value
instead of the empty string at the end of the Regex.Replace
callback. You could also throw an exception.{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.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();
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...".
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", $"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);
}
It allows formatting the replacement inline as well. For example, try changing {FinishTime}
to {FinishTime:HH:mm:ss}
in emailTemplate
.
如果您需要一些非常强大的东西(但实际上不是最简单的方法),您可以托管 ASP.NET 并将其用作您的模板引擎。
您将拥有 ASP.NET 的所有功能来格式化您的消息正文。
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.