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?
Utilisez un moteur de modèles. StringTemplate est l'un d'entre eux, et il y en a beaucoup.
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).
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.
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.
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.
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)
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();
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}
.
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
, \\
Et \{
Et les ai codées en dur. Vous pouvez facilement en ajouter d'autres ou les rendre définissables par le consommateur.RegexOptions.IgnoreCase
.Match.Value
Au lieu de la chaîne vide à la fin du rappel Regex.Replace
. Vous pouvez également lever une exception.{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.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é ...".
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 #.
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.