Co jest dobrym sposobem robienia szablonów ciągów in.NET?

Muszę wysyłać powiadomienia e-mail do użytkowników i muszę zezwolić administratorowi na dostarczenie szablonu dla treści wiadomości(i ewentualnie nagłówków).

Chciałbym coś w rodzaju string.Format, które pozwala mi na podanie nazwanych ciągów zastępczych, więc szablon może wyglądać tak:

Dear {User},

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

Regards,

-- 
{Signature}

Jaki jest najprostszy sposób dla mnie, aby to zrobić?

Author: Simon, 2009-04-09

12 answers

Użyj silnika szablonów. StringTemplate jest jednym z nich i jest ich wiele.

Przykład:

using Antlr.StringTemplate;
using Antlr.StringTemplate.Language;
 
StringTemplate hello = new StringTemplate("Hello, $name$", typeof(DefaultTemplateLexer));
hello.SetAttribute("name", "World");
Console.Out.WriteLine(hello.ToString());
 26
Author: Anton Gogolev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-07 10:51:32

Oto wersja dla tych z Was, którzy mogą korzystać z nowej wersji 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}."

W wierszu-jest to teraz w pełni obsługiwana funkcja języka (interpolacja ciągów znaków).

 40
Author: Benjamin Gruenbaum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-26 09:06:29

Możesz użyć " string.Format " Metoda:

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

Pozwala na zmianę treści w przyszłości i jest przyjazny dla lokalizacji.

 21
Author: TcKs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-08 18:42:10

Bazując na odpowiedzi Benjamina Gruenbauma, w C# w wersji 6 można dodać @ z $ i praktycznie używać kodu tak, jak jest, np.:

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

...i można ich używać w / align = "left" /

: o)

 14
Author: mrrrk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-26 06:41:36

Napisałem całkiem prostą bibliotekę, SmartFormat , która spełnia wszystkie twoje wymagania. Koncentruje się na komponowaniu tekstu w języku naturalnym i świetnie nadaje się do generowania danych z list lub stosowania logiki warunkowej.

Składnia jest bardzo podobna do String.Format i jest bardzo prosta i łatwa do nauczenia się i użycia. Oto przykład składni z dokumentacji:

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

Biblioteka ma świetne opcje obsługi błędów(ignoruj błędy, błędy wyjściowe, błędy rzucania). Oczywiście, to byłoby idealne dla Twojego przykładu.

Biblioteka jest open source i łatwo rozszerzalna, więc możesz ją również wzbogacić o dodatkowe funkcje.

 14
Author: Scott Rippey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-22 22:27:01

Możesz użyć string.Replace(...), ostatecznie w for-each przez wszystkie słowa kluczowe. Jeśli jest tylko kilka słów kluczowych, możesz je mieć w linii takiej jak Ta:

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

Lub możesz użyć Regex.Replace(...), jeśli potrzebujesz czegoś nieco mocniejszego i z większą ilością opcji.

Przeczytaj ten artykuł na codeproject , aby zobaczyć, która opcja zastępowania łańcuchów jest dla Ciebie najszybsza.

 9
Author: Ovi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-09 09:28:42

Bardzo proste rozwiązanie oparte na regex. Obsługuje sekwencje specjalne w stylu \n oraz zmienne nazwane w stylu {Name}.

Źródło

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

Użycie

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

Uwagi

  • zdefiniowałem tylko \n, \r, \\ i \{ sekwencje ucieczki i zakodowanie ich na twardo. Możesz łatwo dodać więcej lub uczynić je definiowalnymi przez konsumenta.
  • uczyniłem nazwy zmiennych niewrażliwymi na wielkość liter, ponieważ takie rzeczy są często przedstawiane do użytkownicy końcowi/nie-Programiści i osobiście nie uważam, że rozróżnianie wielkości liter ma sens w tym przypadku użycia-to tylko jeszcze jedna rzecz, którą mogą się pomylić i zadzwonić do ciebie, aby narzekać (plus ogólnie, jeśli uważasz, że potrzebujesz rozróżniania wielkości liter, to naprawdę potrzebujesz lepszych nazw symboli). Aby rozróżnić wielkość liter, po prostu usuń znacznik RegexOptions.IgnoreCase.
  • usuwam niepoprawne nazwy zmiennych i sekwencje escape z łańcucha wynikowego. Aby pozostawić je nienaruszone, zwróć Match.Value zamiast pusty łańcuch na końcu wywołania zwrotnego Regex.Replace. Możesz też rzucić wyjątek.
  • użyłem składni {var}, ale może to kolidować z natywną interpolowaną składnią łańcuchów. Jeśli chcesz zdefiniować szablony w literałach łańcuchowych w kodzie, zaleca się zmianę ograniczników zmiennych na np. %var% (regex \\.|%([a-z0-9_.\-]+)%) lub inną wybraną składnię, która jest bardziej odpowiednia do danego przypadku użycia.
 6
Author: DaveRandom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-29 08:18:39

Właściwie, możesz użyć XSLT. Tworzysz prosty szablon 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>

Następnie utwórz XmlDocument, aby wykonać transformację przeciwko: 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);

Następnie zastosuj transformację:

        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();
 5
Author: 0100110010101,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-09 09:43:08

Implementacja własnego niestandardowego formatera może być dobrym pomysłem.

Oto Jak to zrobisz. Najpierw Utwórz typ, który definiuje rzeczy, które chcesz wprowadzić do wiadomości. Uwaga: mam zamiar zilustrować to tylko z częścią użytkownika szablonu...
class JobDetails
{
    public string User 
    { 
        get;
        set; 
    }        
}

Następnie zaimplementuj prosty niestandardowy program do formatowania...

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

Wreszcie, użyj go w ten sposób...

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

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

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

... który wygeneruje tekst " Dear Martin Peck. Twoja robota skończona...".

 5
Author: Martin Peck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-09 09:50:28

Jeśli potrzebujesz czegoś bardzo potężnego (, ale naprawdę nie najprostszy sposób) możesz hostować ASP.NET i użyj go jako silnika szablonów.

You ' ll have all the power of ASP.NET aby sformatować treść wiadomości.

 1
Author: thinkbeforecoding,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-08 18:41:03

Jeśli kodujesz w VB.NET możesz używać liter XML. Jeśli kodujesz w C# możesz użyć ShartDevelop aby mieć pliki w VB.NET w tym samym projekcie co kod C#.

 1
Author: epitka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-08 18:44:30

W przypadku, gdy ktoś szuka alternatywy-prawdziwej. NET:

Https://github.com/crozone/FormatWith

Ładne proste rozwiązanie do rozbudowy. Dziękuję crozone!

Tak więc użycie rozszerzenia ciągu podanego w FormatWith tutaj są dwa przykłady:

    /// Use a dictionary that has the tokens as keys with values for the replacement
    /// OR
    /// Use a poco with properties that match the replacement tokens

//////////////////////////////////

        public void testEmailFormatWith()
        {
            var emailDictionary = new Dictionary<string, object>()
            {
                { "User", "Simon" },
                { "FinishTime", DateTime.Now },
                { "FileUrl", new Uri("http://example.com/dictionary") },
                { "Signature", $"Sincerely,{Environment.NewLine}Admin" }
            };

            var emailTemplate = @"
Dear {User},

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

Regards,

-- 
{Signature}
            ";

            {
                var emailBody = emailTemplate.FormatWith(emailDictionary);

                System.Console.WriteLine(emailBody);
            }
            {
                var emailBody = emailTemplate.FormatWith(new MessageValues());

                System.Console.WriteLine(emailBody);
            }
        }

//////////////////////////////////

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

Umożliwia również formatowanie zastępczej linii wewnętrznej. Na przykład spróbuj zmienić {FinishTime} na {FinishTime:HH:mm:ss}.

 1
Author: jimnkey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-21 20:18:23