views:

39

answers:

3

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

A: 

You can loop through the properties by calling typeof(User).GetProperties().

SLaks
+1  A: 

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

Tomas Petricek
You can also use prop.GetValue(user, null) instead of prop.GetGetMethod().Invoke(...)
Fadrian Sudaman
Would it be possible to wrap this into a method that takes ANY object?
Blankman
@Fadrian: Thanks, that's a good suggestion! @Blankman: Yes, it can work with any object. Either wrap it into a generic method and use `typeof(T)` (if the object type is known at compile-time), or use `user.GetType()` to get the current type of argument of type `object` at runtime.
Tomas Petricek
A: 

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