I have a class like
public class MyClass {
public string FirstName {get; set;}
public string LastName {get; set;}
}
The case is, I have a string text like,
string str = "My Name is #MyClass.FirstName #MyClass.LastName";
what I want is to replace #MyClass.FirstName & #MyClass.LastName with values using reflection which are assigned to objects FirstName and LastName in class.
Any help please?
If you want to generate the string you can use Linq to enumerate the properties:
MyClass test = new MyClass {
FirstName = "John",
LastName = "Smith",
};
String result = "My Name is " + String.Join(" ", test
.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(property => property.CanRead) // Not necessary
.Select(property => property.GetValue(test)));
// My Name is John Smith
Console.Write(result);
In case you want to substitute within the string (kind of formatting), regular expressions can well be your choice in order to parse the string:
String original = "My Name is #MyClass.FirstName #MyClass.LastName";
String pattern = "#[A-Za-z0-9\\.]+";
String result = Regex.Replace(original, pattern, (MatchEvaluator) ((match) =>
test
.GetType()
.GetProperty(match.Value.Substring(match.Value.LastIndexOf('.') + 1))
.GetValue(test)
.ToString() // providing that null can't be returned
));
// My Name is John Smith
Console.Write(result);
Note, that in order to get instance (i. e. not static) property value you have to provide the instance (test in the code above):
.GetValue(test)
so #MyClass part in the string is useless, since we can get type directly from instance:
test.GetType()
Edit: in case that some properties can return null as value
String result = Regex.Replace(original, pattern, (MatchEvaluator) ((match) => {
Object v = test
.GetType()
.GetProperty(match.Value.Substring(match.Value.LastIndexOf('.') + 1))
.GetValue(test);
return v == null ? "NULL" : v.ToString();
}));
First of all, I'd advice against using reflection when other options such as string.Format is possible. Reflection can make your code less readable and harder to maintain. Either way, you could do it like this:
public void Main()
{
string str = "My Name is #MyClass.FirstName #MyClass.LastName";
var me = new MyClass { FirstName = "foo", LastName = "bar" };
ReflectionReplace(str, me);
}
public string ReflectionReplace<T>(string template, T obj)
{
foreach (var property in typeof(T).GetProperties())
{
var stringToReplace = "#" + typeof(T).Name + "." + property.Name;
var value = property.GetValue(obj);
if (value == null) value = "";
template = template.Replace(stringToReplace, value.ToString());
}
return template;
}
This should require no additional changes if you want to add a new property to your class and update your template string to include the new values. It should also handle any properties on any class.
Using Reflection you can achieve it as shown below
MyClass obj = new MyClass() { FirstName = "Praveen", LaseName = "Paulose" };
string str = "My Name is #MyClass.FirstName #MyClass.LastName";
string firstName = (string)obj.GetType().GetProperty("FirstName").GetValue(obj, null);
string lastName = (string)obj.GetType().GetProperty("LaseName").GetValue(obj, null);
str = str.Replace("#MyClass.FirstName", firstName);
str = str.Replace("#MyClass.LastName", lastName);
Console.WriteLine(str);
You are first finding the relevant Property using GetProperty and then its value using GetValue
UPDATE
Based on further clarification requested in the comment
You could use a regex to identify all placeholders in your string. i.e. #MyClass.Property. Once you have found them you can use Type.GetType to get the Type information and then use the code shown above to get the properties. However you will need the namespace to instantiate the types.
Although this is five years old, I found it useful.
Here is my adaptation of Dmitry Bychenko's that also dives into any JSON fields as well.
Only works on one level, but I'll make it recursive at some point.
Regex _replacementVarsRegex = new Regex(#"\{\{([\w\.]+)\}\}", RegexOptions.Compiled);
var result = "string with {{ObjectTypeName.PropertyName}} and json {{ObjectTypeName.JsonAsStringProperty.JsonProperty}}";
string ReplaceFor(object o) => _replacementVarsRegex.Replace(result, match =>
{
if (o == null) return match.Value;
var typeName = match.Value.Substring(0, match.Value.IndexOf('.')).TrimStart('{');
var type = o.GetType();
if (typeName != type.Name) return match.Value;
var name = match.Value.Substring(match.Value.LastIndexOf('.') + 1).TrimEnd('}');
var propertyInfo = type.GetProperty(name);
var value = propertyInfo?.GetValue(o);
var s = value?.ToString();
if (match.Value.Contains(".Metadata."))
{
var jsonProperty = type.GetProperty("Metadata");
var json = jsonProperty?.GetValue(o)?.ToString() ?? string.Empty;
if (!string.IsNullOrWhiteSpace(json))
{
// var dObj = JsonConvert.DeserializeObject(json);
var jObj = JObject.Parse(json);
var val = jObj[name].Value<string>();
s = val;
}
}
return s ?? match.Value;
});
result = ReplaceFor(object1);
result = ReplaceFor(object2);
//...
result = ReplaceFor(objectn);
//remove any not answered, if desired
result = _replacementVarsRegex.Replace(result, string.Empty);
improvement suggestions welcome!
Working on a project recently I ended up needing something similar. The requirement was to use a placeholder format that wouldn't naturally occur in our text #obj.property may have so #obj.property# was used. Also, handling of cascading tokens (tokens depending on tokens) and handling of data coming from IEnumerable, such as: ["Cat", "Dog", "Chicken"] needing to displayed in the text as "Cat, Dog, Chicken".
I've created a library and have a NuGet package to get this functionality into your code a little quicker.
Related
I have a scenario where I am supposed to add a feature to the existing code and this feature is supposed to take input of from the user and form a string. Right now by default the code ships with full name = firstname and lastname. But I am supposed to make it configurable according to user demand, where the user can add any property like location or phone number to display with the full name. So for e.g the format can be [firstname + lastname + location or lastname + firstname + phonenumber ].
And I have managed to take the users input and store it in a variable called test and here is the code for it.
[DataMember]
public string FullName
{
get
{
string test = "";
test = Services.GetService<IGlobalOptionsBrokerDataAccess>().test1();
return string.Format("{0} {1} {2}", this.FirstName, this.MiddleName, this.LastName);
}
set
{
_fullName = value;
}
}
So how can I make it work dynamically? Here the screenshot of how the value is available in the test variable. If the user wants to have ManagerID then how can I make it work dynamically?
Is there anything more I should provide so that it would be easier for you guys out there?
May not be an optimal solution, but this is what I thought of quickly. Making your format string dynamic could help you.
var posDict = new Dictionary<string, string> {
{"FirstName","{0}" },
{"MiddleName","{1}" },
{"LastName","{2}" }};
var test = "FirstName,LastName,MiddleName";
var posString = "";
foreach (var prop in test.Split(','))
posString += $"{posDict.First(x => x.Key == prop).Value} ";
return string.Format(posString, this.FirstName, this.MiddleName, this.LastName);
I found a better solution other than the mentioned above.
string[] columnNames = format.Split(new char[] { ',' });
string userFullNameAsPerFormat = string.Empty;
string defaultFullName = this.FirstName + " " + this.LastName;
// Get the Type object corresponding to MyClass.
Type myType = typeof(Courion.BusinessServices.Access.ProfileDTO);
// Get the PropertyInfo object by passing the property name.
PropertyInfo myPropInfo = myType.GetProperty(item.Trim());
userFullNameAsPerFormat += (string) myPropInfo.GetValue(this) + " ";
I have the following problem.
I have these strings with whitespace between them.
"+name:string" "+age:int"
I split them with this code:
List<string> stringValueList = new List<string>();
stringValueList = System.Text.RegularExpressions.Regex.Split(stringValue, #"\s{2,}").ToList<string>();
now the elements of List looks like this
"+name:string"
"+age:int"
Now I want to split these strings and create Objects.
This looks like this:
// Storing the created objects in a List of objects
List<myObject> objectList = new List<myObject>();
for(i = 1; i < stringValueList.Count ; i+=2)
{
myObject object = new myObject();
object.modifier = '+';
object.name = stringValueList[i-1].Trim('+'); // out of the example the object.name should be "name"
object.type = stringValueList[i]; // out of the example the object.type value should "string"
objectList.Add(object);
}
At the end I should get two objects with these values:
List<myObject> objectList{ myObject object1{modifier = '+' , name ="name" , type="string"}, myObject object2{modifier='+', name="age" type="int"}}
But my result looks like this:
List<myObject> objectList {myObject object1 {modifier='+', name="name:string" type="+age:int"}}
So instead of getting 2 Objects, I am getting 1 Object. It puts both strings into the elements of the first object.
Can anyone help me out? I guess my problem is in the for loop because i-1 value is the first string in the List and i is the second string but I cant change this.
I guess my problem is in the for loop because i-1 value is the first string in the List and i is the second string but I cant change this.
I don't know why you do i += 2, because apparently you want to split each string in two again. So just have to change that.
Use foreach(), and inside your loop, split your string again:
foreach (var stringValue in stringValueList)
{
myObject object = new myObject();
var kvp = stringValue.Split(':');
object.modifier = '+';
object.name = kvp[0].Trim('+');
object.type = kvp[1];
objectList.Add(object);
}
Of course this code assumes your inputs are always valid; you'd have to add some boundary checks to make it more robust.
Alternatively, you could expand your Regex formula to do the whole thing in one go.
For example, with (?<=")[+](.*?):(.*?)(?="), all you'd have to do is assign the matched group values.
foreach (Match m in Regex.Matches(stringValue, "(?<=\")[+](.*?):(.*?)(?=\")"))
{
myObject obj = new myObject
{
modifier = '+',
name = m.Groups[1].Value,
type = m.Groups[2].Value
};
objectList.Add(obj);
}
It's interesting to see how others approach a problem. I would have done something like this:
public class MyObject
{
public char Modifier { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public static IEnumerable<MyObject> Parse(string str)
{
return str
.Split(' ')
.Where(s => string.IsNullOrEmpty(s) == false)
.ToList()
.ForEach(i =>
{
var sections = i.Remove(0, 1).Split(':');
return new MyObject()
{
Modifier = i[0],
Name = sections[0],
Type = sections[1]
};
});
}
}
Is there a good way to replace placeholders with dynamic data ?
I have tried loading a template and then replaced all {{PLACEHOLDER}}-tags, with data from the meta object, which is working.
But if I need to add more placeholders I have to do it in code, and make a new deployment, so if it is possible I want to do it through the database, like this:
Table Placeholders
ID, Key (nvarchar(50), Value (nvarchar(59))
1 {{RECEIVER_NAME}} meta.receiver
2 {{RESOURCE_NAME}} meta.resource
3 ..
4 .. and so on
the meta is the name of the parameter sent in to the BuildTemplate method.
So when I looping through all the placeholders (from the db) I want to cast the value from the db to the meta object.
Instead of getting "meta.receiver", I need the value inside the parameter.
GetAllAsync ex.1
public async Task<Dictionary<string, object>> GetAllAsync()
{
return await _context.EmailTemplatePlaceholders.ToDictionaryAsync(x => x.PlaceholderKey, x => x.PlaceholderValue as object);
}
GetAllAsync ex.2
public async Task<IEnumerable<EmailTemplatePlaceholder>> GetAllAsync()
{
var result = await _context.EmailTemplatePlaceholders.ToListAsync();
return result;
}
sample not using db (working))
private async Task<string> BuildTemplate(string template, dynamic meta)
{
var sb = new StringBuilder(template);
sb.Replace("{{RECEIVER_NAME}}", meta.receiver?.ToString());
sb.Replace("{{RESOURCE_NAME}}", meta.resource?.ToString());
return sb.ToString();
}
how I want it to work
private async Task<string> BuildTemplate(string template, dynamic meta)
{
var sb = new StringBuilder(template);
var placeholders = await _placeholders.GetAllAsync();
foreach (var placeholder in placeholders)
{
// when using reflection I still get a string like "meta.receiver" instead of meta.receiver, like the object.
// in other words, the sb.Replace methods gives the same result.
//sb.Replace(placeholder.Key, placeholder.Value.GetType().GetField(placeholder.Value).GetValue(placeholder.Value));
sb.Replace(placeholder.Key, placeholder.Value);
}
return sb.ToString();
}
I think it might be a better solution for this problem. Please let me know!
We have solved similar issue in our development.
We have created extension to format any object.
Please review our source code:
public static string FormatWith(this string format, object source, bool escape = false)
{
return FormatWith(format, null, source, escape);
}
public static string FormatWith(this string format, IFormatProvider provider, object source, bool escape = false)
{
if (format == null)
throw new ArgumentNullException("format");
List<object> values = new List<object>();
var rewrittenFormat = Regex.Replace(format,
#"(?<start>\{)+(?<property>[\w\.\[\]]+)(?<format>:[^}]+)?(?<end>\})+",
delegate(Match m)
{
var startGroup = m.Groups["start"];
var propertyGroup = m.Groups["property"];
var formatGroup = m.Groups["format"];
var endGroup = m.Groups["end"];
var value = propertyGroup.Value == "0"
? source
: Eval(source, propertyGroup.Value);
if (escape && value != null)
{
value = XmlEscape(JsonEscape(value.ToString()));
}
values.Add(value);
var openings = startGroup.Captures.Count;
var closings = endGroup.Captures.Count;
return openings > closings || openings%2 == 0
? m.Value
: new string('{', openings) + (values.Count - 1) + formatGroup.Value
+ new string('}', closings);
},
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
return string.Format(provider, rewrittenFormat, values.ToArray());
}
private static object Eval(object source, string expression)
{
try
{
return DataBinder.Eval(source, expression);
}
catch (HttpException e)
{
throw new FormatException(null, e);
}
}
The usage is very simple:
var body = "[{Name}] {Description} (<a href='{Link}'>See More</a>)";
var model = new { Name="name", Link="localhost", Description="" };
var result = body.FormatWith(model);
You want to do it like this:
sb.Replace(placeholder.Key, meta.GetType().GetField(placeholder.Value).GetValue(meta).ToString())
and instead of meta.reciever, your database would just store receiver
This way, the placeholder as specified in your database is replaced with the corresponding value from the meta object. The downside is you can only pull values from the meta object with this method. However, from what I can see, it doesn't seem like that would be an issue for you, so it might not matter.
More clarification: The issue with what you tried
//sb.Replace(placeholder.Key, placeholder.Value.GetType().GetField(placeholder.Value).GetValue(placeholder.Value));
is that, first of all, you try to get the type of the whole string meta.reciever instead of just the meta portion, but then additionally that there doesn't seem to be a conversion from a string to a class type (e.g. Type.GetType("meta")). Additionally, when you GetValue, there's no conversion from a string to the object you need (not positive what that would look like).
As you want to replace all the placeholders in your template dynamically without replacing them one by one manually. So I think Regex is better for these things.
This function will get a template which you want to interpolate and one object which you want to bind with your template. This function will automatically replace your placeholders like
{{RECEIVER_NAME}} with values in your object.
You will need a class which contain all the properties that you want to bind. In this example by class is MainInvoiceBind.
public static string Format(string obj,MainInvoiceBind invoice)
{
try
{
return Regex.Replace(obj, #"{{(?<exp>[^}]+)}}", match =>
{
try
{
var p = Expression.Parameter(typeof(MainInvoiceBind), "");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, match.Groups["exp"].Value);
return (e.Compile().DynamicInvoke(invoice) ?? "").ToString();
}
catch
{
return "Nill";
}
});
}
catch
{
return string.Empty;
}
}
I implement this technique in a project where I hade to generates email dynamically from there specified templates. Its working good for me. Hopefully, Its solve your problem.
I updated habibs solution to the more current System.Linq.Dynamic.Core NuGet package, with small improvements.
This function will automatically replace your placeholders like {{RECEIVER_NAME}} with data from your object. You can even use some operators, since it's using Linq.
public static string Placeholder(string input, object obj)
{
try {
var p = new[] { Expression.Parameter(obj.GetType(), "") };
return Regex.Replace(input, #"{{(?<exp>[^}]+)}}", match => {
try {
return DynamicExpressionParser.ParseLambda(p, null, match.Groups["exp"].Value)
.Compile().DynamicInvoke(obj)?.ToString();
}
catch {
return "(undefined)";
}
});
}
catch {
return "(error)";
}
}
You could also make multiple objects accessible and name them.
So I have a situation where I need to dynamically update properties of my object based on values contained. In the case below, I need to update the value with replacing the first two characters of the current value with a different string if condition is true.
PersonDetail.EvaluateConditionalRule("ID",
"((ID.Length > Convert.ToInt32(#0) ) AND ID.Substring(Convert.ToInt32(#1), Convert.ToInt32(#2)) == #3 )",
new[] { "1", "0", "2", "SS" }, " ID = (#0 + ID.Substring(Convert.ToInt32(#1))) " , new[] { "98", "2" });
public static void EvaluateConditionalRule(this PersonDetail Detail, String PropertyToEvaluate,
String ConditionalExpression, String[] parameters, String IfTrueExpression,String[] IfTrueExpreassionparameters )
{
var property = Detail.GetType().GetProperties().Where(x => x.Name == PropertyToEvaluate).FirstOrDefault();
if (property == null)
throw new InvalidDataException(String.Format("Please specify a valid {0} property name for the evaluation.", Detail.GetType()));
//put together the condition like so
if (new[] { Detail }.AsQueryable().Where(ConditionalExpression, parameters).Count() > 0 && IfTrueExpression != null)
{
var result = new[] { Detail }.AsQueryable().Select(IfTrueExpression, IfTrueExpreassionparameters);
//Stuck Here as result does not contain expected value
property.SetValue( Detail,result , null);
}
}
Essentially what I want is, to be able to execute expressions fed this, and I don't think I have the format right for the substring replace expression to correctly evaluate. What I want from the above is something like
ID = "98"+ ID.Substring(2);
Any help will be appreciated. Thanks
Please give us some information on why you need the updates to be this dynamic. Do you want the user to enter the condition strings by hand?
About your code:
Your selector string is wrong. In the LINQ Dynamic Query Library there is a special syntax for it. Please look that up in the Documentation (see paragraph Expression Language and Data Object Initializers) provided with the sample: http://msdn.microsoft.com/en-US/vstudio/bb894665.aspx
I wrote a small sample:
var id = new string[] { "SS41231" }.AsQueryable();
// *it* represents the current element
var res = id.Where("it.Length > #0 AND it.Substring(#1, #2) = #3", 1, 0, 2, "SS"); // Save the result, don't throw it away.
if (res.Any())
{
// Line below in normal LINQ: string newID = res.Select(x => "98" + x.Substring(2)).First();
string newId = res.Select("#0 + it.Substring(#1)", "98", 2).Cast<string>().First();
Console.WriteLine(newId);
}
Please write some feedback.
Greetings.
Not sure what exactly you mean saying "dynamically" but i suppose the approach with lambdas does not fit you:
static void Main(string[] args) {
User user = new User { ID = 5, Name = "Test" };
SetNewValue(user, u => u.Name, s => s.StartsWith("T"), s => s + "123");
}
static void SetNewValue<TObject, TProperty>(TObject obj, Func<TObject, TProperty> propertyGetter, Func<TProperty, bool> condition, Func<TProperty, TProperty> modifier) {
TProperty property = propertyGetter(obj);
if (condition(property)) {
TProperty newValue = modifier(property);
//set via reflection
}
}
So I would recommend you using Expression trees which allow you to build any runtime construction you like, for part of your example
var exp = Expression.Call(Expression.Constant("test"), typeof(string).GetMethod("Substring", new[] { typeof(int) }), Expression.Constant(2));
Console.WriteLine(Expression.Lambda(exp).Compile().DynamicInvoke()); //prints "st"
However if you want to use strings with raw c# code as expressions check the CSharpCodeProvider class
public struct Parameter
{
public Parameter(string name, string type, string parenttype)
{
this.Name = name;
this.Type = type;
this.ParentType = parenttype;
}
public string Name;
public string Type;
public string ParentType;
}
Following values are stored in the array of Parameter:
Name Type ParentType
-------------------------------------------------------
composite CompositeType
isThisTest boolean
BoolValue boolean CompositeType
StringValue string CompositeType
AnotherType AnotherCompositeType CompositeType
account string AnotherCompositeType
startdate date AnotherCompositeType
I want to read this to build an xml. something like:
<composite>
<BoolValue>boolean</BoolValue>
<StringValue>string</StringValue>
<AnotherType>
<account>string</account>
<startdate>date</startdate>
</AnotherType>
<composite>
<isThisTest>boolean</isThisTest>
I am using the following logic to read the values:
foreach (Parameter parameter in parameters)
{
sb.Append(" <" + parameter.Name + ">");
//HERE: need to check parenttype and get all it's child elements
//
sb.Append("</" + parameter.Name + ">" + CrLf);
}
Is there a simpler way to read the array to get the parents and thier child? May be using LINQ? I still on .Net 3.5. Appreciate any suggestions with example code :)
You could write a little recursive method to deal with this :
IEnumerable<XElement> GetChildren ( string rootType, List<Parameter> parameters )
{
return from p in parameters
where p.ParentType == rootType
let children = GetChildren ( p.Type, parameters )
select children.Count() == 0 ?
new XElement ( p.Name, p.Type ) :
new XElement ( p.Name, children );
}
Each call builds up an Enumerable of XElements which contains the parameters whose parent has the passed in type. The select recurses into the method again finding the children for each Element.
Note that this does assume that the data is correctly formed. If two parameters has eachother as a parent you will get a Stack Overflow.
The magic is in the XElements class (Linq to Xml) that accepts enumerables of XElements to build up the tree like Xml structure.
The first call, pass null (or use default parameters if using C# 4) as the rootType. Use like :
void Main()
{
var parameters = new List<Parameter> {
new Parameter {Name = "composite", Type = "CompositeType" },
new Parameter {Name = "isThisTest", Type = "boolean" },
new Parameter {Name = "BoolValue", Type = "boolean", ParentType = "CompositeType" },
new Parameter {Name = "StringValue", Type = "string", ParentType = "CompositeType" },
new Parameter {Name = "AnotherType", Type = "AnotherCompositeType", ParentType = "CompositeType" },
new Parameter {Name = "account", Type = "string", ParentType = "AnotherCompositeType" },
new Parameter {Name = "startdate", Type = "date", ParentType = "AnotherCompositeType" }
};
foreach ( var r in GetChildren ( null, parameters ) )
{
Console.WriteLine ( r );
}
}
Output :
<composite>
<BoolValue>boolean</BoolValue>
<StringValue>string</StringValue>
<AnotherType>
<account>string</account>
<startdate>date</startdate>
</AnotherType>
</composite>
<isThisTest>boolean</isThisTest>
Edit
In response to your comment, XElement gives you two options for outputting as a string.
ToString() will output formatted Xml.
ToString(SaveOptions) allows you to specify formatted or unformatted output as well as ommitting duplicate namespaces.
I'm sure you could probably adapt the solution to use StringBuilder if you really had to, although it probably wouldn't be as elegant..
It looks like you want to use a recursive method, something like:
string GetChildren(Parameter param, string indent)
{
StringBuilder sb = new StringBuilder();
if (HasChildren(param))
{
sb.AppendFormat("{0}<{1}>{2}", indent, param.Name, Environment.NewLine);
foreach (Parameter child in parameters.Where(p => p.ParentType == param.Type))
{
sb.Append(GetChildren(child, indent + " "));
}
sb.AppendFormat("{0}</{1}>{2}", indent, param.Name, Environment.NewLine);
}
else
{
sb.AppendFormat("{0}<{1}>{2}</{1}>{3}", indent, param.Name, param.Type, Environment.NewLine);
}
return sb.ToString();
}
The method that looks to see whether a Parameter has child nodes would look like:
bool HasChildren(Parameter param)
{
return parameters.Any(p => p.ParentType == param.Type);
}
The collection parameters could be defined as an IEnumerable<Parameter> and could be implemented using a List<Parameter>.