web-dev-qa-db-fra.com

Quelle est la bonne façon de créer des modèles de chaîne dans .NET?

Je dois envoyer des notifications par e-mail aux utilisateurs et je dois autoriser l'administrateur à fournir un modèle pour le corps du message (et éventuellement des en-têtes également).

J'aimerais quelque chose comme string.Format qui me permet de donner des chaînes de remplacement nommées, de sorte que le modèle puisse ressembler à ceci:

Dear {User},

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

Regards,

-- 
{Signature}

Quel est le moyen le plus simple pour moi de le faire?

42
Simon

Utilisez un moteur de modèles. StringTemplate est l'un d'entre eux, et il y en a beaucoup.

21
Anton Gogolev

Voici la version pour ceux d'entre vous qui peuvent utiliser une nouvelle version de 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}."

En une ligne - c'est maintenant une fonctionnalité de langue entièrement prise en charge (interpolation de chaînes).

33
Benjamin Gruenbaum

Vous pouvez utiliser la méthode "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);

Il vous permet de modifier le contenu à l'avenir et est convivial pour la localisation.

16
TcKs

SmartFormat est une bibliothèque assez simple qui répond à toutes vos exigences. Il se concentre sur la composition de texte en "langage naturel" et est idéal pour générer des données à partir de listes ou appliquer une logique conditionnelle.

La syntaxe est extrêmement similaire à String.Format, et est très simple et facile à apprendre et à utiliser. Voici un exemple de la syntaxe de la documentation:

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

La bibliothèque dispose d'excellentes options de gestion des erreurs (ignorer les erreurs, les erreurs de sortie, les erreurs de lancement). De toute évidence, cela fonctionnerait parfaitement pour votre exemple.

La bibliothèque est open source et facilement extensible, vous pouvez donc également l'améliorer avec des fonctionnalités supplémentaires.

13
Scott Rippey

Vous pouvez utiliser string.Replace (...), éventuellement dans un for-each à travers tous les mots-clés. S'il n'y a que quelques mots clés, vous pouvez les avoir sur une ligne comme celle-ci:

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

Ou vous pouvez utiliser Regex.Replace (...), si vous avez besoin de quelque chose d'un peu plus puissant et avec plus d'options.

Lisez ceci article sur codeproject pour voir quelle option de remplacement de chaîne est la plus rapide pour vous.

10
Ovi

En s'appuyant sur la réponse de Benjamin Gruenbaum, dans C # version 6, vous pouvez ajouter un @ avec le $ et utiliser à peu près votre code tel qu'il est, par exemple:

var text = $@"Dear {User},

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

Regards,

-- 
{Signature}
";

Le $ est pour l'interpolation de chaînes: https://docs.Microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated

Le @ est l'identifiant textuel: https://docs.Microsoft.com/en-us/dotnet/csharp/language-reference/tokens/verbatim

... et vous pouvez les utiliser conjointement.

: o)

8
mrrrk

En fait, vous pouvez utiliser XSLT. Vous créez un modèle XML simple:

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

Créez ensuite un XmlDocument pour effectuer une transformation par rapport à: 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);

Après cela, appliquez la 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();
5
0100110010101

Une solution très simple basée sur des regex. Prend en charge les séquences d'échappement à caractère unique de style \n Et les variables nommées de style {Name}.

La 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

Remarques

  • J'ai uniquement défini les séquences d'échappement \n, \r, \\ Et \{ Et les ai codées en dur. Vous pouvez facilement en ajouter d'autres ou les rendre définissables par le consommateur.
  • J'ai rendu les noms de variables insensibles à la casse, car des choses comme celle-ci sont souvent présentées aux utilisateurs finaux/non-programmeurs et je ne pense pas personnellement que la sensibilité à la casse ait un sens dans ce cas d'utilisation - c'est juste une chose de plus qu'ils peut se tromper et vous téléphoner pour vous plaindre (plus en général si vous pensez que vous avez besoin de noms de symboles sensibles à la casse, ce dont vous avez vraiment besoin sont de meilleurs noms de symboles). Pour les rendre sensibles à la casse, supprimez simplement le drapeau RegexOptions.IgnoreCase.
  • Je supprime les noms de variables invalides et les séquences d'échappement de la chaîne de résultat. Pour les laisser intacts, retournez Match.Value Au lieu de la chaîne vide à la fin du rappel Regex.Replace. Vous pouvez également lever une exception.
  • J'ai utilisé la syntaxe {var}, Mais cela peut interférer avec la syntaxe de chaîne interpolée native. Si vous souhaitez définir des modèles dans des littéraux de chaîne dans votre code, il peut être judicieux de changer les délimiteurs de variable par exemple. %var% (Regex \\.|%([a-z0-9_.\-]+)%) ou une autre syntaxe de votre choix qui est plus appropriée au cas d'utilisation.
4
DaveRandom

L'implémentation de votre propre formateur personnalisé peut être une bonne idée.

Voici comment procéder. Créez d'abord un type qui définit les éléments que vous souhaitez injecter dans votre message. Remarque: je vais seulement illustrer cela avec la partie utilisateur de votre modèle ...

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

Ensuite, implémentez un simple formateur personnalisé ...

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

Enfin, utilisez-le comme ça ...

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

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

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

... qui va générer le texte "Cher Martin Peck. Votre travail est terminé ...".

4
Martin Peck

Si vous codez dans VB.NET, vous pouvez utiliser des littéraux XML. Si vous codez en C #, vous pouvez utiliser ShartDevelop pour avoir des fichiers dans VB.NET dans le même projet que le code C #.

1
epitka

Si vous avez besoin de quelque chose de très puissant (mais vraiment pas le moyen le plus simple), vous pouvez héberger ASP.NET et l'utiliser comme moteur de création de modèles.

Vous aurez toute la puissance d'ASP.NET pour formater le corps de votre message.

1