number interpolacion cadenas antes c# .net string templating

interpolacion - string.format c#



¿Cuál es una buena manera de hacer plantillas de cadenas en.NET? (12)

Necesito enviar notificaciones por correo electrónico a los usuarios y debo permitir que el administrador proporcione una plantilla para el cuerpo del mensaje (y posiblemente encabezados, también).

Me gustaría algo como una string.Format que me permite dar cadenas de reemplazo con nombre, para que la plantilla se vea así:

Dear {User}, Your job finished at {FinishTime} and your file is available for download at {FileURL}. Regards, -- {Signature}

¿Cuál es la forma más sencilla de hacerlo?


Aquí está la versión para aquellos de ustedes que pueden usar una nueva versión 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 una línea: ahora es una función de idioma totalmente compatible (interpolación de cadenas).


Basándose en la respuesta de Benjamin Gruenbaum, en la versión 6 de C # puede agregar un @ con $ y usar su código tal como está, por ejemplo:

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


En realidad, puedes usar XSLT. Usted crea una plantilla 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>

A continuación, cree un XmlDocument para realizar la transformación en contra de: 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);

Después de eso, aplica la transformación:

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



Implementar su propio formateador personalizado podría ser una buena idea.

Así es como lo haces. Primero, crea un tipo que defina las cosas que deseas inyectar en tu mensaje. Nota: Solo voy a ilustrar esto con la parte de Usuario de su plantilla ...

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

A continuación, implemente un formateador personalizado simple ...

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

Finalmente, úsalo así ...

string template = "Dear {0:User}. Your job finished..."; JobDetails job = new JobDetails() { User = "Martin Peck" }; string message = string.Format(new ExampleFormatter(), template, job);

... que generará el texto "Querido Martin Peck. Tu trabajo terminó ...".


Podría usar string.Replace (...) eventualmente en for-each a través de todas las palabras clave. Si solo hay unas pocas palabras clave, puede tenerlas en una línea como esta:

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

O podría usar Regex.Replace (...), si necesita algo un poco más poderoso y con más opciones.

Lea este artículo en codeproject para ver qué opción de reemplazo de cadena es la más rápida para usted.


Puede usar el método "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);

Le permite cambiar el contenido en el futuro y es amigable para la localización.


Si está codificando en VB.NET puede usar literales XML. Si está codificando en C #, puede usar ShartDevelop para tener archivos en VB.NET en el mismo proyecto que el código C #.


Si necesita algo muy poderoso ( pero realmente no de la manera más simple ), puede alojar ASP.NET y usarlo como su motor de plantillas.

Tendrá todo el poder de ASP.NET para formatear el cuerpo de su mensaje.


Una solución muy simple basada en expresiones regulares. Admite secuencias de escape de caracteres individuales de /n -style y variables con nombre de estilo {Name} .

Fuente

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

Uso

var text = $@"Dear {User}, Your job finished at {FinishTime} and your file is available for download at {FileURL}. Regards, -- {Signature} ";

Notas

  • Solo he definido /n , /r , // y /{ secuencias de escape y las he codificado. Puede agregar fácilmente más o hacer que el consumidor pueda definirlos.
  • He hecho que los nombres de variables sean insensibles a las mayúsculas y minúsculas, ya que a menudo se presentan a usuarios finales / no programadores y personalmente no creo que la sensibilidad a mayúsculas y minúsculas tenga sentido en ese caso de uso, es solo una cosa más que puede equivocarse y llamarte para quejarse (más, en general, si crees que necesitas nombres de mayúsculas y minúsculas, lo que realmente necesitas son mejores nombres de símbolos). Para que RegexOptions.IgnoreCase mayúsculas y minúsculas, simplemente elimine el indicador RegexOptions.IgnoreCase .
  • Elimino nombres de variables inválidas y secuencias de escape de la cadena de resultados. Para dejarlos intactos, devuelva Match.Value lugar de la cadena vacía al final de Regex.Replace callback. También podría lanzar una excepción.
  • He usado la sintaxis de {var} , pero esto puede interferir con la sintaxis de la cadena interpolada nativa. Si desea definir plantillas en literales de cadenas en su código, puede ser recomendable cambiar los delimitadores de variables, por ejemplo, %var% (regex //.|%([a-z0-9_./-]+)% ) o alguna otra sintaxis de su elección que sea más apropiada para el caso de uso.

Use un motor de plantillas. StringTemplate es uno de esos, y hay muchos.


SmartFormat es una biblioteca bastante simple que cumple con todos sus requisitos. Está enfocado en componer texto en "lenguaje natural" y es ideal para generar datos a partir de listas o aplicar lógica condicional.

La sintaxis es muy similar a String.Format , y es muy simple y fácil de aprender y usar. Aquí hay un ejemplo de la sintaxis de la documentación:

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

La biblioteca tiene grandes opciones de manejo de errores (ignorar errores, errores de salida, errores de tiro). Obviamente, esto funcionaría perfecto para tu ejemplo.

La biblioteca es de código abierto y fácilmente extensible, por lo que también puede mejorarla con funciones adicionales.