Currently I am doing this:
I have text that looks like:
Hello ${user.name}, this is ....
And I do this:
public string TransformUser(User user, string text)
{
StringBuilder sb = new StringBuilder(text);
sb.Replace("${user.name}", user.Name);
...
...
return sb.ToString();
}
Is there a bette开发者_JAVA百科r way, maybe using reflection somehow to loop through the classes public properties?
Edit
Would it be possible to make this method Generic, so I can pass any object to it?
Looping through all properties using reflection and replacing the keys in the string would look roughly like this:
var args = new object[0];
foreach(var prop in typeof(User).GetProperties()) {
if (prop.CanRead) {
string val = prop.GetGetMethod().Invoke(user, args).ToString();
sb.Replace("${user." + prop.Name +"}", val);
}
}
It uses CanRead
to check if the property has getter and then invokes the getter to read the value. A value is converted to string simply using ToString
which will probably work for primitive types (depending on the required behavior). This is case sensitive, so you may want to use ToLower
if the users write keys using lowercase (as in your example).
I wrote a StringTemplate
class which could probably be modified to suit your needs... It behaves like String.Format
, with a major difference : you can use names for placeholders, rather than indexes. The values to format can be specified as either a IDictionary<string, object>
, or any object (in that case each placeholder will be replaced with the value of the property with the same name).
For instance :
// with a dictionary :
var values = new Dictionary<string, object>
{
{ "Title", "Mr." },
{ "LastName", "Smith" }
};
string a = StringTemplate.Format("Hello {Title} {LastName}", values);
// with an anonymous type :
string b = StringTemplate.Format(
"Hello {Title} {LastName}",
new { Title = "Mr.", LastName = "Smith" });
If you need to use the same template several times, you can create an instance of StringTemplate
and reuse it for better performance (the template string will be parsed only once).
You can also specify format modifiers, like in String.Format
.
To fit your exact needs, this class will need a few adjustments, but it shouldn't be too hard...
Here's the code :
public class StringTemplate
{
private string _template;
private static Regex _regex = new Regex(@"(?<open>{+)(?<key>\w+)(?<format>:[^}]+)?(?<close>}+)", RegexOptions.Compiled);
public StringTemplate(string template)
{
template.CheckArgumentNull("template");
this._template = template;
ParseTemplate();
}
private string _templateWithIndexes;
private List<string> _placeholders;
private void ParseTemplate()
{
_placeholders = new List<string>();
MatchEvaluator evaluator = (m) =>
{
if (m.Success)
{
string open = m.Groups["open"].Value;
string close = m.Groups["close"].Value;
string key = m.Groups["key"].Value;
string format = m.Groups["format"].Value;
if (open.Length % 2 == 0)
return m.Value;
open = RemoveLastChar(open);
close = RemoveLastChar(close);
if (!_placeholders.Contains(key))
{
_placeholders.Add(key);
}
int index = _placeholders.IndexOf(key);
return string.Format("{0}{{{1}{2}}}{3}", open, index, format, close);
}
return m.Value;
};
_templateWithIndexes = _regex.Replace(_template, evaluator);
}
private string RemoveLastChar(string str)
{
if (str.Length > 1)
return str.Substring(0, str.Length - 1);
else
return string.Empty;
}
public static implicit operator StringTemplate(string s)
{
return new StringTemplate(s);
}
public override string ToString()
{
return _template;
}
public string Format(IDictionary<string, object> values)
{
values.CheckArgumentNull("values");
object[] array = new object[_placeholders.Count];
for(int i = 0; i < _placeholders.Count; i++)
{
string key = _placeholders[i];
object value;
if (!values.TryGetValue(key, out value))
{
value = string.Format("{{{0}}}", key);
}
array[i] = value;
}
return string.Format(_templateWithIndexes, array);
}
private IDictionary<string, object> MakeDictionary(object obj)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
Type type = obj.GetType();
foreach (string propName in _placeholders)
{
var prop = type.GetProperty(propName);
if (prop != null)
dict.Add(propName, prop.GetValue(obj, null));
}
return dict;
}
public string Format(object values)
{
return Format(MakeDictionary(values));
}
public static string Format(string template, IDictionary<string, object> values)
{
return new StringTemplate(template).Format(values);
}
public static string Format(string template, object values)
{
return new StringTemplate(template).Format(values);
}
}
You can loop through the properties by calling typeof(User).GetProperties()
.
精彩评论