I'm writing a content management system in ASP.NET/C# which the template of the site is defined in a html file. I am passing parameters to the html file with blocks. A block starts with [ and ends with ]. An example for a template with some simple blocks:
<html>
<head>
<title>[Title]</title>
<meta name="description" content="[Description]" />
</head>
<body>
<h1>[Headline]</h1>
<p>[Text]</p>
</body>
</html>
Now, I've a class which represents the template. The class for the example template looks like this:
public class MyTemplate
{
public string Title
{
get;
set;
}
public string Description
{
get;
set;
}
public string Headline
{
get;
set;
}
public string Text
{
get;
set;
}
}
The class could be a class that I write, a class generated by Linq To SQL or just any class.
I created a method which replaces a block with the value of the property. I'm using regular expression for this purpose:
public static string ParseTemplateFromObject(string input, object obj)
{
return Regex.Replace(input, @"\[(.*?)\]", new MatchEvaluator(delegate(Match match)
{
var blockName = match.Result("$1");
return obj.GetType().GetProperties().SingleOrDefault(p => p.Name.Equals(blockName, StringComparison.OrdinalIgnoreCase))
.GetValue(obj, null).ToString();
}), RegexOptions.Multiline);
}
...and it is working. I'm using GetProperties() and then Linq instead of GetProperty to prevent case sensitivity. But now I've another problem, when I want to use parameters in a block. For example: I want to create a vertical menu. The menu in the system can be vertical or horizontal:
[Menu Id=1, Direction="Vertical"]
So, I decided that this type of block calls a method and extracts it's value, instead of extracting the value from it's property. Example:
public class MyTemplate
{
...
public string Menu(int id, string direction)
{
string menu = ...;
return menu;
}
}
I extended my ParseTemplateFromObject
to support this:
public static string ParseTemplateFromObject(string input, object obj)
{
return Regex.Replace(input, @"\[(.*?)\]", new MatchEvaluator(delegate(Match match)
{
var blockName = match.Result("$1");
var m = Regex.Match(blockName, "(?<Name>\\w+)=((?<Value>\\w+)|\"(?<Value>([^\"]*))\")");
if (m.Captures.Count > 0)
{
var method = obj.GetType().GetMethods().Single(p => p.Name.Equals(blockName.Substring(0, blockName.IndexOf(' '))
, StringComparison.OrdinalIgnoreCase));
var methodParameters = method.GetParameters();
var parameters = new object[methodParameters.Length];
while (m.Success)
{
var name = m.Groups["Name"].Value;
var value = m.Groups["Value"].Value;
var methodParameter = methodParameters.Single(p => p.Name.ToLower() == name.ToLower());
parameters[methodParameter.Position] =
Convert.ChangeType(value, methodParameter.ParameterType);
m = m.NextMatch();
}
return method.Invoke(obj, parameters).ToString();
}
else
{
return obj.GetType().GetProperties().SingleOrDefault(p => p.Name.Equals(blockName, StringComparison.OrdinalIgnoreCase))
.GetValue(obj, null).ToString();
}
}), RegexOptions.Multiline);
}
}
It's working, but I'm looking for ways to make it more effective. And I don't know if it is actually the right way to implement this?
Thank you.