Ответ 1
Используйте шаблонный движок. StringTemplate является одним из тех, и их много.
Мне нужно отправить уведомления по электронной почте пользователям, и я должен разрешить администратору предоставить шаблон для тела сообщения (и, возможно, заголовки тоже).
Мне бы хотелось что-то вроде string.Format
, которое позволяет мне указывать именованные строки замены, поэтому шаблон может выглядеть так:
Dear {User},
Your job finished at {FinishTime} and your file is available for download at {FileURL}.
Regards,
--
{Signature}
Какой самый простой способ сделать это?
Используйте шаблонный движок. StringTemplate является одним из тех, и их много.
Вот версия для тех из вас, кто может использовать новую версию С#:
// add $ at start to mark string as template
var template = $"Your job finished at {FinishTime} and your file is available for download at {FileURL}."
В строке - теперь это полностью поддерживаемая функция языка (интерполяция строк).
Вы можете использовать метод 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);
Он позволяет вам изменять контент в будущем и дружелюбен для локализации.
SmartFormat - довольно простая библиотека, которая отвечает всем вашим требованиям. Он ориентирован на составление текста "естественного языка" и отлично подходит для генерации данных из списков или применения условной логики.
Синтаксис очень похож на String.Format
, и он очень прост и прост в освоении и использовании. Вот пример синтаксиса из документации:
Smart.Format("{Name} friends: {Friends:{Name}|, |, and}", user)
// Result: "Scott friends: Michael, Jim, Pam, and Dwight"
В библиотеке реализованы отличные возможности обработки ошибок (игнорируются ошибки, ошибки вывода, ошибки броска). Очевидно, это будет работать идеально для вашего примера.
Библиотека с открытым исходным кодом и легко расширяемая, поэтому вы также можете улучшить ее с помощью дополнительных функций.
Вы можете использовать string.Replace(...), в конце концов, для каждого из всех ключевых слов. Если есть несколько ключевых слов, вы можете иметь их в строке следующим образом:
string myString = template.Replace("FirstName", "John").Replace("LastName", "Smith").Replace("FinishTime", DateTime.Now.ToShortDateString());
Или вы можете использовать Regex.Replace(...), если вам нужно что-то более мощное и с большим количеством опций.
Прочитайте статью о кодебере, чтобы узнать, какой вариант замены строк наиболее быстрый для вас.
Основываясь на ответе Бенджамина Грюнбаума, в С# версии 6 вы можете добавить @с символом $ и в значительной степени использовать свой код как есть, например:
var text = [email protected]"Dear {User},
Your job finished at {FinishTime} and your file is available for download at {FileURL}.
Regards,
--
{Signature}
";
$
предназначен для интерполяции строк: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated
@
является дословным идентификатором: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/verbatim
... и вы можете использовать их вместе.
: О)
Собственно, вы можете использовать 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 = новый 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();
Реализация собственного пользовательского форматирования может быть хорошей идеей.
Вот как вы это делаете. Во-первых, создайте тип, определяющий материал, который вы хотите ввести в ваше сообщение. Примечание. Я собираюсь проиллюстрировать это с пользовательской частью вашего шаблона...
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);
..., который будет генерировать текст "Дорогой Мартин Пек. Ваша работа закончена...".
Там есть хороший фон, с реализацией, в блоге Phil Haack: http://haacked.com/archive/2009/01/14/named-formats-redux.aspx
Очень простое решение на основе регулярных выражений. Поддерживает \n
-стильные последовательности escape-символов одного символа и {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
, \\
и \{
escape-последовательности и их жестко закодированные. Вы можете легко добавить больше или сделать их определяемыми потребителем.RegexOptions.IgnoreCase
.Match.Value
вместо пустой строки в конце обратного вызова Regex.Replace
. Вы также можете исключить исключение.{var}
, но это может помешать синтаксису родной интерполированной строки. Если вы хотите определить шаблоны в строковых литералах в вашем коде, может быть целесообразно изменить разделители переменных на, например. %var%
(regex \\.|%([a-z0-9_.\-]+)%
) или какой-либо другой синтаксис по вашему выбору, который более подходит для использования.Если вам нужно что-то очень мощное (но на самом деле не самый простой способ), вы можете разместить ASP.NET и использовать его в качестве шаблона.
У вас будет вся мощь ASP.NET для форматирования тела вашего сообщения.
Если вы кодируете в VB.NET, вы можете использовать литералы XML. Если вы кодируете на С#, вы можете использовать ShartDevelop для хранения файлов в VB.NET в том же проекте, что и код С#.