Какой хороший способ создания шаблонов строк в .NET? - PullRequest
40 голосов
/ 09 апреля 2009

Мне нужно отправлять уведомления по электронной почте пользователям, и я должен позволить администратору предоставить шаблон для тела сообщения (и, возможно, заголовков тоже).

Я бы хотел что-то вроде string.Format, которое позволяет мне давать именованные строки замены, чтобы шаблон мог выглядеть так:

Dear {User},

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

Regards,

-- 
{Signature}

Какой самый простой способ для меня это сделать?

Ответы [ 12 ]

30 голосов
/ 26 июня 2015

Вот версия для тех из вас, кто может использовать новую версию 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}."

В строке - теперь это полностью поддерживаемая функция языка (интерполяция строк).

19 голосов
/ 09 апреля 2009

Используйте шаблонизатор. StringTemplate является одним из них, и их много.

14 голосов
/ 09 апреля 2009

Вы можете использовать метод "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);

Позволяет изменять контент в будущем и удобен для локализации.

13 голосов
/ 06 ноября 2011

SmartFormat - довольно простая библиотека, которая отвечает всем вашим требованиям. Он ориентирован на составление текста на «естественном языке» и отлично подходит для генерации данных из списков или применения условной логики.

Синтаксис очень похож на String.Format, он очень прост и легок в изучении и использовании. Вот пример синтаксиса из документации:

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

Библиотека имеет большие возможности обработки ошибок (игнорирование ошибок, вывод ошибок, ошибок выбрасывания). Очевидно, это подойдет для вашего примера.

Библиотека имеет открытый исходный код и легко расширяема, поэтому вы также можете расширить ее с помощью дополнительных функций.

10 голосов
/ 09 апреля 2009

Вы можете использовать string.Replace (...), в конце концов, for-each через все ключевые слова. Если есть только несколько ключевых слов, вы можете разместить их в следующей строке:

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

Или вы можете использовать Regex.Replace (...), если вам нужно что-то более мощное и с большим количеством опций.

Прочтите эту статью о codeproject , чтобы узнать, какой вариант замены строки самый быстрый для вас.

6 голосов
/ 17 февраля 2017

Опираясь на ответ Бенджамина Грюнбаума, в C # версии 6 вы можете добавить @ с символом $ и в значительной степени использовать свой код, как он есть, например ::100100

var text = $@"Dear {User},

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

Regards,

-- 
{Signature}
";
5 голосов
/ 09 апреля 2009

На самом деле, вы можете использовать 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();
4 голосов
/ 08 января 2016

Очень простое решение на основе регулярных выражений. Поддерживает escape-последовательности из одного символа \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

Примечания

  • Я только определил escape-последовательности \n, \r, \\ и \{ и жестко запрограммировал их. Вы можете легко добавить больше или сделать их определяемыми потребителем.
  • Я сделал имена переменных нечувствительными к регистру, поскольку подобные вещи часто представляются конечным пользователям / непрограммистам, и я лично не думаю, что чувствительность к регистру имеет смысл в этом случае использования - это всего лишь один Более того, они могут ошибиться и позвонить вам, чтобы жаловаться (плюс, в общем, если вы считаете, что вам нужны регистрозависимые имена символов, то, что вам действительно нужно, это лучшие имена символов). Чтобы сделать их чувствительными к регистру, просто удалите флаг RegexOptions.IgnoreCase.
  • Я удаляю недопустимые имена переменных и escape-последовательности из строки результата. Чтобы оставить их нетронутыми, верните Match.Value вместо пустой строки в конце обратного вызова Regex.Replace. Вы также можете вызвать исключение.
  • Я использовал синтаксис {var}, но это может помешать синтаксису нативной интерполированной строки. Если вы хотите определить шаблоны в строковых литералах в своем коде, может быть целесообразно изменить разделители переменных, например, на. %var% (regex \\.|%([a-z0-9_.\-]+)%) или другой выбранный вами синтаксис, более подходящий для варианта использования.
4 голосов
/ 09 апреля 2009

В блоге Фила Хаака есть хорошая история с реализацией: http://haacked.com/archive/2009/01/14/named-formats-redux.aspx

4 голосов
/ 09 апреля 2009

Реализация собственного пользовательского форматера может быть хорошей идеей.

Вот как ты это делаешь. Сначала создайте тип, который определяет материал, который вы хотите добавить в ваше сообщение. Примечание. Я проиллюстрирую это только пользовательской частью шаблона ...

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

..., который сгенерирует текст "Дорогой Мартин Пек. Ваша работа закончена ...".

...