public class UFMLine
{
public UFMTemplate elementTag;
public int posX1;
public int posY1;
public int posX2;
public int posY2;
public string property;
public string hierStr = string.Empty;
public List<UFMLine> ufmLines = new List<UFMLine>(); // the tricky nested class field
}
UFMLine ufmobj = new UFMLine();
This ufmobj is populated perfectly at the window load.
In my button click of wpf window xaml code behind...
string nthItem = "ufmobj.ufmLines[0].ufmLines[1].ufmLines[1].ufmLines[1].ufmLines[2].ufmLines[2].elementTag";
// Tried reflection method, but giving null exception
var result = typeof(UFMLine).GetField(nthItem).GetValue(ufmobj);
So when opening watch window and fetch the value for nthItem name it gives appropriate value.
How to get it in the code behind or am i not properly using the reflection?
Thanks.
The "name" of the variable (ufmobj) could be a problem since it could be lost in a release build, but the rest can be achieved with reflection if you don't mind implementing your own parser.
Split path (Syntax of your choice)
Resolve one path segment after another
Determine if path is Field/Property/Indexer/Method/etc.
Repeat until done
Here is a small snippet to get you going (far from complete, but works with your example):
Resolve a single Field or Property
private object GetFieldOrProperty(object obj, string name)
{
Type objType = obj.GetType();
if (objType.GetField(name) != null)
return objType.GetField(name).GetValue(obj);
if (objType.GetProperty(name) != null)
return objType.GetProperty(name).GetValue(obj, null);
return null;
}
Resolve the entire path:
private object Resolve(object parent, string path)
{
string[] paths = path.Split('.');
foreach (string p in paths)
{
if (p.EndsWith("]"))
{
int start = p.IndexOf("[");
string property = p.Substring(0, start);
string index = p.Substring(start + 1, p.Length - start - 2);
parent = GetFieldOrProperty(parent, property);
if (parent == null)
return null;
foreach (PropertyInfo info in parent.GetType().GetProperties())
{
if (info.GetIndexParameters().Length < 1) continue;
parent = info.GetValue(parent, new object[] {int.Parse(index)});
break;
}
}
else
{
parent = GetFieldOrProperty(parent, p);
if (parent == null)
return null;
}
}
return parent;
}
Test case:
UFMLine ufmobj = new UFMLine();
ufmobj.ufmLines.Add(new UFMLine());
ufmobj.ufmLines[0].ufmLines.Add(new UFMLine());
ufmobj.ufmLines[0].ufmLines[0].ufmLines.Add(new UFMLine{property = "Success"});
Debug.WriteLine(Resolve("ufmLines[0].ufmLines[0].ufmLines[0].property.Length", ufmobj));
7 (length of "Success")
You may change the fields of your UFMLine class to public properties:
public class UFMLine
{
public UFMTemplate elementTag { get; set; }
public int posX1 { get; set; }
public int posY1 { get; set; }
public int posX2 { get; set; }
public int posY2 { get; set; }
public string property { get; set; }
public string hierStr { get; set; } = string.Empty;
public List<UFMLine> ufmLines { get; set; } = new List<UFMLine>();
}
Now you could use the part of your nthItem string after "ufmobj." as the Path of a Binding, for which you would use ufmobj as Source:
var binding = new Binding
{
Source = ufmobj,
Path = new PropertyPath("ufmLines[0].ufmLines[1].ufmLines[1].ufmLines[1].ufmLines[2].ufmLines[2].elementTag")
};
To make use of this Binding, you would also need a target dependency property, which you might declare in a helper class like this:
public class BindingHelper : DependencyObject
{
public static readonly DependencyProperty ResultProperty =
DependencyProperty.Register("Result", typeof(object), typeof(MainWindow));
public object Result
{
get { return GetValue(ResultProperty); }
set { SetValue(ResultProperty, value); }
}
}
Finally you would assign the Binding to the Target property and then retrieve the resulting value like this:
var bindingHelper = new BindingHelper();
BindingOperations.SetBinding(bindingHelper, BindingHelper.ResultProperty, binding);
var result = bindingHelper.Result;
Related
public class Person {
string Name
string Address
int Age
.. 100+ more columns
}
var result = new List<Person>();
foreach (var item in result )
{
//loop column and trim the values.
}
I want the simplest way to loop the columns (assuming 100+ columns) where datatype is string then trim the value.
To rephrase in more C# terms: I want to update all properties and fields of an object that are of type string with trimmed value as item.StringProp = item.StringProp.Trim(). I don't want to manually write update for each property.
You could use reflection and Linq for filtering the properties of type string. From the OP, it looks like you are using Fields instead of properties. Please note it is unclear whether the Properties/Fields are public from OP, if you need to use public fields/properties, please use BindingFlags.Public
public List<T> TrimList<T>(List<T> source)
{
foreach(var property in typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(x=>x.FieldType== typeof(string)))
{
foreach(var personItem in source)
property.SetValue(personItem,Convert.ToString(property.GetValue(personItem)).Trim());
}
return source;
}
If properties, you could use
public List<T> TrimList<T>(List<T> source)
{
foreach(var property in typeof(T).GetProperties(BindingFlags.NonPublic | BindingFlags.Instance).Where(x=>x.PropertyType== typeof(string)))
{
foreach(var personItem in source)
property.SetValue(personItem,Convert.ToString(property.GetValue(personItem)).Trim());
}
return source;
}
Demo Code
Note: prior to .NET 4.5 you need to pass null as a second argument:
public List<T> TrimList<T>(List<T> source)
{
foreach(var property in typeof(T).GetProperties(BindingFlags.NonPublic | BindingFlags.Instance).Where(x=>x.PropertyType== typeof(string)))
{
foreach(var personItem in source)
property.SetValue(personItem,Convert.ToString(property.GetValue(personItem,null)).Trim());
}
return source;
}
As well as reflection, another way is to make it the responsibility of the Person class.
public class Person {
public string Name { get; set; }
public string Address { get; set; }
public int Age { get; set; }
.. 100+ more columns
public void DoTrim()
{
this.Name = this.Name.Trim();
this.Address = this.Address.Trim();
... still need to code 100+ properties
}
}
The advantage is that you can call it like this
var result = new List<Person>();
...
for(int i=0; i < result.Count(); i++)
{
result[i].DoTrim();
}
Or you can control your data in the Person class when you set it and use local private variables.
public class Person {
private string name;
public string Name
{
get { return name; }
set { name = value.Trim(); }
}
private string address;
public string Address
{
get { return address; }
set { address= value.Trim(); }
}
....
This is how I would implement it:
class Program
{
static void Main(string[] args)
{
var obj = new Person
{
MyProperty = " A",
MyProperty1 = " A ",
MyProperty2 = "A ",
MyProperty3 = "A A A",
};
TrimStrings(obj);
}
public static void TrimStrings(object obj)
{
Type stringType = typeof(string);
var properties = obj.GetType().GetProperties().Where(x => x.PropertyType == stringType);
foreach(var property in properties)
{
string value = (string)property.GetValue(obj);
property.SetValue(obj, value?.Trim());
}
}
}
public class Person
{
public string MyProperty { get; set; }
public string MyProperty1 { get; set; }
public string MyProperty2 { get; set; }
public string MyProperty3 { get; set; }
}
Output:
{"MyProperty":"A","MyProperty1":"A","MyProperty2":"A","MyProperty3":"A
A A"}
You can use This Nuget Package
.After Install use it as bellow:
result.ForEach(x => x.AdjustString());
I have this json string and i want to get the 4th line (iValue, sValue) of every record.
My problem here is the keys vary for every record (based on the data type of the value).
Is there any way to do this on C#?
Here is an example:
{ "data": [
{
"pKey": "0",
"Entity": "tableName",
"Attribute": "CID",
"iValue": "13"
},
{
"pKey": "0",
"Entity": "tableName",
"Attribute": "username",
"sValue": "test_user1"
}] }
Here is kind of a big implementation, you will have to implement this for each iValue, fValue, etc however, it speeds up the implementation and usage. First of, here is the usage:
string rawJson = "{\"data\":[{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"CID\",\"iValue\":\"13\"},{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"username\",\"sValue\":\"test_user1\"}]}";
var values = JsonConvert.DeserializeObject<TakeData>(rawJson).Data.Select(v => v.PureData);
Now values contains the list. Here is the usage for accessing each:
foreach (var val in values)
{
if (val is IntData i)
{
int myInt = i.iValue;
// use the rest of the properties
}
else if (val is StrData s)
{
string myStr = s.sValue;
// use the rest of the properties
}
}
And here is the implementation:
class TakeData
{
public List<TakeItAll> Data { get; set; }
}
class TakeItAll
{
public int pKey { get; set; }
public string Entity { get; set; }
public string Attribute { get; set; }
private int _iValue;
public int iValue
{
get => _iValue;
set
{
_iValue = value;
PureData = new IntData { pKey = pKey, Entity = Entity, Attribute = Attribute, iValue = iValue };
}
}
private string _sValue;
public string sValue
{
get => _sValue;
set
{
_sValue = value;
PureData = new StrData { pKey = pKey, Entity = Entity, Attribute = Attribute, sValue = sValue };
}
}
public IPureData PureData { get; private set; }
}
interface IPureData
{
int pKey { get; set; }
string Entity { get; set; }
string Attribute { get; set; }
}
class IntData : IPureData
{
public int pKey { get; set; }
public string Entity { get; set; }
public string Attribute { get; set; }
public int iValue { get; set; }
}
class StrData : IPureData
{
public int pKey { get; set; }
public string Entity { get; set; }
public string Attribute { get; set; }
public string sValue { get; set; }
}
Of course you can use some alternatives as well. Such as using an enum in TakeItAll to keep track of the data type (or a type variable) instead of so many classes. This way However the size of the values object would be larger.
class TakeItAll
{
public int pKey { get; set; }
public string Entity { get; set; }
public string Attribute { get; set; }
private int _iValue;
public int iValue
{
get => _iValue;
set
{
_iValue = value;
ValType = typeof(string);
}
}
private string _sValue;
public string sValue
{
get => _sValue;
set
{
_sValue = value;
ValType = typeof(int);
}
}
public Type ValType { get; private set; }
}
I would deserialize this into an object supporting both types of properties and then by code try parsing either the integer or the string if the integer fails.
If the Attribute value gives you a clue as to which one to look for, you could also use that to prevent having to try parsing the integer every time.
I would not rely on the property being the "fourth" property every time, as I'm assuming this would be external data, where you may not be able to control whether these properties come out in the exact same order every time (now and in the future).
If you don't know the data type then you could use an object to handle it.
It's a good idea to deserialize JSON string to concrete class to avoid string manipulation mistake.
public class Datum
{
public object pKey { get; set; }
public string Entity { get; set; }
public string Attribute { get; set; }
public string iValue { get; set; }
public string sValue { get; set; }
}
public class DataCollection
{
public List<Datum> data { get; set; }
}
public void Test()
{
var str = "{\"data\":[{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"CID\",\"iValue\":\"13\"},{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"username\",\"sValue\":\"test_user1\"}]}";
var list = JsonConvert.DeserializeObject<DataCollection>(str);
var keys = list.data.Select(x => x.pKey).ToList();
}
Another option is to deserialize to dynamic and inspect that:
var json = "{\"data\":[{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"CID\",\"iValue\":\"13\"},{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"username\",\"sValue\":\"test_user1\"}]}";
var result = JsonConvert.DeserializeAnonymousType<dynamic>(json, null);
if (result.data != null)
{
for (var i = 0; i < result.data.Count; i++)
{
if (result.data[i]["iValue"] != null)
// Parse iValue
if (result.data[i]["sValue"] != null)
// Parse sValue
}
}
You could load the Json in a ExpandoObject
var expConverter = new ExpandoObjectConverter();
dynamic objList = JsonConvert.DeserializeObject<List<ExpandoObject>>(json, expConverter);
JSON array to ExpandoObject via JSON.NET
Then once you have loaded it in as a List<ExpandoObject> you may itterate over it as a dictionary.
foreach(var obj in objList)
{
//convert the object to a Dictionary and select the 4th element.
var yourresult = (obj as IDictionary<string, object>).ElementAt(3);
}
My two cents:
Get each object of the array as a KeyValuePair
var json = "your json here";
var root = JsonConvert.DeserializeObject<Root>(json);
foreach (var element in root.Data)
{
//===================================================> Here using object because your value type change. You can change it to string if your value is always wrapped in a string (like "13")
var keyValuePair = element.ToObject<Dictionary<string, object>>();
//here, for each object of the 'data' array, you can check if the desidered property exists
if (keyValuePair.ContainsKey("iValue"))
{
var propertyValue = keyValuePair["iValue"];
}
else if (keyValuePair.ContainsKey("sValue"))
{
var propertyValue = keyValuePair["sValue"];
}
// Or you can check the property name in the desidered position
if (keyValuePair.Keys.ElementAt(3) == "iValue")
{
var propertyValue = keyValuePair["iValue"];
}
else if (keyValuePair.Keys.ElementAt(3) == "sValue")
{
var propertyValue = keyValuePair["sValue"];
}
}
Where Root is
class Root
{
[JsonProperty("data")]
public List<JObject> Data { get; set; }
}
With this solution you can always know which property (iValue or sValue) is specified. On the contrary, if you use a model class which has both property names, you wouldn't know which property is specified when the value is null (unless you use additional properties/classes and a custom JsonConverter).
Edit
As Panagiotis Kanavos reminded me, the JObject class implements IDictionary<string, JToken>. So, inside your foreach you could use:
if (element["iValue"] != null)
{
var propertyValue = element["iValue"].Value<string>();
}
if (element["sValue"] != null)
{
var propertyValue = element["sValue"].Value<string>();
}
// Or you can check the property name in the desidered position
var propName = element.Properties().ElementAt(3).Name;
if (propName == "iValue")
{
var propertyValue = keyValuePair["iValue"].Value<string>();
}
else if (propName == "sValue")
{
var propertyValue = keyValuePair["sValue"].Value<string>();
}
Of course you can optimize this code and check for nulls.
I have following two classes
public class Family
{
public string ChildName { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Family Child { get; set; }
}
I have an instance of Employee class as follows.
Employee employee = new Employee();
employee.Name = "Ram";
employee.Id = 77;
employee.Child = new Family() { ChildName = "Lava" };
I have a method which gets the property value based on the property name as follows:
public static object GetPropertyValue(object src, string propName)
{
string[] nameParts = propName.Split('.');
if (nameParts.Length == 1)
{
return src.GetType().GetRuntimeProperty(propName).GetValue(src, null);
}
foreach (String part in nameParts)
{
if (src == null) { return null; }
Type type = src.GetType();
PropertyInfo info = type.GetRuntimeProperty(part);
if (info == null)
{ return null; }
src = info.GetValue(src, null);
}
return src;
}
In the above method,when I try to get property value of nested class like
GetPropertyValue(employee, "employee.Child.ChildName")
or
GetPropertyValue(GetPropertyValue(employee, "Family"), "ChildName"
doesn't return any value because type.GetRuntimeProperty(part) is always null.
Is there any way to fix this problem?
You problem lies in this line:
foreach (String part in nameParts)
Because you are iterating over each part of nameParts, you are also iterating over "employee", which of course is not a valid property.
Try either this:
foreach (String part in nameParts.Skip(1))
Or calling the method like this:
GetPropertyValue(employee, "Child.ChildName")
(Notice no "employee.", because you already pass in an employee)
The problem in this case is that when you split the string employee.Child.ChildName, the "employee" is the first part. However, employee is not a property of the source i.e. Employee Class.
Try this:
public class Program
{
public static void Main()
{
Employee employee = new Employee();
employee.Name = "Ram";
employee.Id = 77;
employee.Child = new Family() { ChildName = "Lava" };
GetPropertyValue(employee, "employee.Child.ChildName");
}
public class Family
{
public string ChildName { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Family Child { get; set; }
}
public static object GetPropertyValue(object src, string propName)
{
string[] nameParts = propName.Split('.');
if (nameParts.Length == 1)
{
return src.GetType().GetRuntimeProperty(propName).GetValue(src, null);
}
nameParts = nameParts.Skip(1).ToArray();
foreach (String part in nameParts)
{
if (src == null) { return null; }
Type type = src.GetType();
PropertyInfo info = type.GetRuntimeProperty(part);
if (info == null)
{ return null; }
src = info.GetValue(src, null);
}
return src;
}
Here, i have skipped the first part of the string i.e. "employee". However, you can solve the problem by passing Child.ChildName
This question is around 2 years old, but I found a another working solution for you question, which is easy to understand. If you initialize the object in calling calss constructor you can use dot(.) notation to assign or read property. Example -
public class Family{
public string ChildName { get; set; }
}
public class Employee{
public int Id { get; set; }
public string Name { get; set; }
public Family Child { get; set; }
public Employee(){
Child = new Family();
}
}
Employee emp = new Employee();
emp.Family.ChildName = "Nested calss attribute value";
I used code from this post to implement feature toggles in an application and it is working great. However, now instead of my features being a straight bool on/off, each feature has two bool values: one for the general setting and one for the setting which is only applied to a subset of users. I've been trying to update the code to handle this and I'm finding my basic understanding of reflection and accessors is coming up short.
I'm using the code below to test in LINQPad - basically, compared to the post mentioned above, all I've done is tried to change TestFeatureToggle from a bool, to type MultiToggle but I'm struggling to Set or Get the value of the bool properties of the MultiToggle object. I get an exception at
return (bool)multiToggleProperty.GetValue(null,null);
"Non-static method requires a target". Which makes sense because the properties on MultiToggle are non-static. However, I don't have an instance of those objects and I don't understand how I would get one from a static class.
Any help would be really appreciated. My gut says it might not be possible!
void Main()
{
var exampleFeatureToggles = new List<FeatureToggle>
{
new FeatureToggle {Description = "Test", Id = 1, IsActive = true, Name = "TestFeatureToggle"}
};
FeatureToggles.SetFeatureToggles(exampleFeatureToggles);
Console.WriteLine(FeatureToggles.GetFeatureToggleSetting("TestFeatureToggle"));
}
public class FeatureToggle
{
public string Description { get; set; }
public int Id { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
}
public class MultiToggle
{
public bool DefaultValue { get; private set; }
public bool OtherValue { get; private set; }
}
public static class FeatureToggles
{
//public static bool TestFeatureToggle { get; private set; }
public static MultiToggle TestFeatureToggle { get; private set; }
public static void SetFeatureToggles(List<FeatureToggle> toggles)
{
if (toggles == null) return;
var properties = typeof(FeatureToggles).GetProperties(BindingFlags.Public | BindingFlags.Static);
// All properties must be set to false to prevent a property staying true when deleted from the database
foreach (var property in properties)
{
var multiToggleProperties = typeof(MultiToggle).GetProperties();
foreach (var multiToggleProperty in multiToggleProperties)
{
multiToggleProperty.SetValue(new MultiToggle(), false, null);
}
}
foreach (var toggle in toggles)
{
foreach (var property in properties)
{
if (property.Name.ToLower() == toggle.Name.ToLower())
{
Type tProp = property.GetType();
var multiToggleProperties = typeof(MultiToggle).GetProperties();
foreach (var multiToggleProperty in multiToggleProperties)
{
Console.WriteLine(multiToggleProperty);
multiToggleProperty.SetValue(new MultiToggle(), toggle.IsActive, null);
}
}
}
}
}
public static bool GetFeatureToggleSetting(string propertyName)
{
var properties = typeof(FeatureToggles).GetProperties(BindingFlags.Public | BindingFlags.Static);
foreach (var property in properties)
{
if (property.Name.ToLower() == propertyName.ToLower())
{
Type tProp = property.GetType();
var multiToggleProperties = typeof(MultiToggle).GetProperties();
Console.WriteLine(multiToggleProperties);
foreach (var multiToggleProperty in multiToggleProperties)
{
Console.WriteLine(multiToggleProperty);
return (bool)multiToggleProperty.GetValue(null, null);
}
}
}
return false;
}
}
The idea was there but you've essentially "just overshot the mark". You are getting the "Non-static method requires a target" error because you are trying to get the value of a property in the value of a static property, without getting the value of the static property first (what a mouthful). i.e. you are going:
get static property type -> get instance property type -> get value of property from static property.
DefaultValue, and OtherValue are instance properties so you will need the instance object first before you can get their value. I made a few tweaks just to show you how to both set the static property and get the values from the static property. You should be able to tweak it from there:
void Main()
{
var exampleFeatureToggles = new List<FeatureToggle>
{
new FeatureToggle {Description = "Test", Id = 1, IsActive = true, Name = "TestFeatureToggle"}
};
FeatureToggles.SetFeatureToggles(exampleFeatureToggles);
Console.WriteLine(FeatureToggles.GetFeatureToggleSetting("TestFeatureToggle"));
}
public class FeatureToggle
{
public string Description { get; set; }
public int Id { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
}
public class MultiToggle
{
public bool DefaultValue { get; private set; }
public bool OtherValue { get; private set; }
}
public static class FeatureToggles
{
//public static bool TestFeatureToggle { get; private set; }
public static MultiToggle TestFeatureToggle { get; private set; }
public static void SetFeatureToggles(List<FeatureToggle> toggles)
{
if (toggles == null) return;
var properties = typeof(FeatureToggles).GetProperties(BindingFlags.Public | BindingFlags.Static);
// All properties must be set to false to prevent a property staying true when deleted from the database
foreach (var property in properties)
{
// first change: set the _property_, not multiToggleProperty
property.SetValue(null, new MultiToggle());
}
foreach (var toggle in toggles)
{
foreach (var property in properties)
{
if (property.Name.ToLower() == toggle.Name.ToLower())
{
Type tProp = property.GetType();
var multiToggleProperties = typeof(MultiToggle).GetProperties();
// second change: create a nee instance, set the values, then set that value on the static property
var newMultiToggle = new MultiToggle();
property.SetValue(null, newMultiToggle);
foreach (var multiToggleProperty in multiToggleProperties)
{
Console.WriteLine(multiToggleProperty);
multiToggleProperty.SetValue(newMultiToggle, toggle.IsActive, null);
}
}
}
}
}
public static bool GetFeatureToggleSetting(string propertyName)
{
var properties = typeof(FeatureToggles).GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
foreach (var property in properties)
{
if (property.Name.ToLower() == propertyName.ToLower())
{
Type tProp = property.GetType();
// third change: get the static value first, then get the value from the properties on that instance.
var value = property.GetValue(null);
var multiToggleProperties = typeof(MultiToggle).GetProperties();
Console.WriteLine(multiToggleProperties);
foreach (var multiToggleProperty in multiToggleProperties)
{
Console.WriteLine(multiToggleProperty);
return (bool)multiToggleProperty.GetValue(value, null); //
}
}
}
return false;
}
}
For a presentation involving six components of a Person object's PersonName, I added an extension and a 'mini view model' (PersonNamePropertyTextBox) to cut down on duplicated code and facilitate data binding.
So in the constructor of the parent view model, I create these mini view models like:
public PimDetailVm(Person person, ..)
{
LastName = new PersonNamePropertyTextBox(
() => Model.GetPersonName().LastName, v => this.UpdatePersonNameProperty(pn => pn.LastName, v))
{
Label = PeopleResources.LastName_Label
};
FirstName = new PersonNamePropertyTextBox(
() => Model.GetPersonName().FirstName, v => this.UpdatePersonNameProperty(pn => pn.FirstName, v))
{
Label = PeopleResources.FirstName_Label
};
... etc.
}
public PersonNamePropertyTextBox LastName { get; private set; }
public PersonNamePropertyTextBox FirstName { get; private set; }
What I would really like now is to be able to do is just pass in the current property, ie "LastName" and the label value, and let the mini view model set the appropriate Getter/Setter delegates, something like:
LastName = new PersonNamePropertyTextBox(vm=>LastName, PeopleResources.LastName_Label);
I am struggling as to how to do this though. Any ideas?
Extension (handle updating the PersonName in the Model)
public static void UpdatePersonNameProperty(this PimDetailVm vm, Expression<Func<PersonName, object>> propertyExpression, string value)
{
var pn = vm.Model.GetPersonName();
var pnProps = pn.GetType().GetProperties();
var subj = ExprHelper.GetPropertyName(propertyExpression);
var subjProp = pnProps.Single(pi => pi.Name.Equals(subj));
var currentVal = subjProp.GetValue(pn, null);
// split if there is nothing to update
if(currentVal==null && value==null) return;
if (currentVal != null && currentVal.Equals(value)) return;
// update the property
var capitalized = value == null ? null : value.Capitalize();
subjProp.SetValue(pn, capitalized, null);
// update the model
vm.Model.SetName(pn);
// broadcast the update
vm.NotifyOfPropertyChange(subj, value);
}
Mini View Model for some property of a PersonName
public class PersonNamePropertyTextBox : TextBoxActionData
{
public PersonNamePropertyTextBox(Func<string> getterFunc, Action<string> setterAction) {
if (getterFunc == null) throw new ArgumentNullException("getterFunc");
if (setterAction == null) throw new ArgumentNullException("setterAction");
GetterFunc = getterFunc;
SetterAction = setterAction;
}
}
Try implementing a binder class to manage the binding. In this case I have used PropertyBinding.
public class PropertyBinding
{
public static PropertyBinding To(ViewModel vm, Name name, string label)
{
return new PropertyBinding { ViewModel = vm, Getter = new Func<string>(delegate() { return name.Value; }), Setter = new Action<string>(delegate(string value) { name.Value = value; }), Label = label };
}
public string Label { get; set; }
public ViewModel ViewModel { get; set; }
public Func<string> Getter { get; set; }
public Action<string> Setter { get; set; }
public string Value
{
get { return this.Get(); }
set { this.Set(value); }
}
internal string Get()
{
// Implement UpdatePersonNamePropert here.
// Maybe convert culture before returning.
return this.Getter();
}
internal void Set(string value)
{
// Maybe convert culture before storing.
this.Setter(value);
}
}
It would be called like:
LastName = new PersonNamePropertyTextBox(PropertyBinding.To(Model, Model.GetPersonName().LastName, PeopleResources.LastName_Label));
Please note that Model.GetPersonName().LastName must return a pointer type not a value type otherwise the LastName can not be updated when the Setter is called. For example:
public sealed class Name
{
public string Value { get; set; }
}
In this example PersonName is implemented as below however your implmentation may be different.
public class PersonName
{
public Name LastName { get; set; }
public Name FirstName { get; set; }
}
Without all your class information and the strong types associated with some of the variables you used it was hard to validate but I think this should get you out of trouble.
Hope this helps.