I want to create a dynamic object based in other object's properties and attributes, but I don't know neither how to add attributes to my dynamic properties, nor how to get them back.
Is that possible?
Here is my code, but it's not working. Thanks!
var expando = new ExpandoObject() as IDictionary<String, Object>;
var props = typeof(Costumer).GetProperties();
foreach (var prop in props)
{
var customAttributes = prop.GetCustomAttributes(true);
var displayAttr = customAttributes.OfType<DisplayAttribute>();
expando.Add(prop.Name, prop.PropertyType);
TypeDescriptor.AddAttributes(expando[prop.Name],displayAttr.SingleOrDefault());
var getAttribute = TypeDescriptor.GetAttributes(expando[prop.Name]);
}
Related
Is there a way to assign the key values of each object in a List to a Dictionary? I want to access that dictionary later to assign the values to properties of a class.
I fetch some values from a BD with inner joins, then I store the values in a list of objects:
IEnumerable<object> units = _unitDetailsQuery.Execute<object>(out totalRecords);
Each object has these properties:
Brand (string)
Model (string)
Code (string)
BranchId (int)
Then I create a Dictionary
IDictionary<string, object> props = new Dictionary<string, object>();
I've seen in another question something like this (i've adapted the snippet to my code):
BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance;
foreach (object unit in units)
{
props = unit.GetType().GetProperties(bindingAttr).ToDictionary
(
propInfo => propInfo.Name,
propInfo => propInfo.GetValue(unit, null)
);
}
But props gets no value after the assignment.
Internally the object is something like this:
So how do I get those values? Thanks in advance.
You can use this extension method, which will reflect over the object's public properties and put them into a dictionary.
public static class ObjectExtensionMethods
{
static public Dictionary<string, object> ToPropertyDictionary(this object o)
{
var d = new Dictionary<string, object>();
foreach (var p in o.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
d.Add(p.Name, p.GetValue(o));
}
return d;
}
}
You could use this on a list of objects like so:
var listOfDictionaries = listOfObjects.Select( o => o.ToPropertyDictionary() );
Giving credit to #John Wu, but since you said you wanted information to be editable, I think the extension method instead has to return string, System.Reflection.PropertyInfo instead. That way, properties can be updated and selected.
public static Dictionary<string, System.Reflection.PropertyInfo> ToPropertyDictionary(object o)
{
var d = new Dictionary<string, System.Reflection.PropertyInfo>();
foreach (var p in o.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
d.Add(p.Name, p);
}
return d;
}
This way, you can get/set the original object's properties. It could no longer be an extension method.
This way, you can edit the object like so. Let's say I have a random object named o with a variable named X that's an integer.
Then, I can get the dictionary.
var dict = ToPropertyDictionary(o);
Setting X with a value of 2 can be done with:
dict("X").SetValue(o, 2);
Getting X then would be done with:
dict("X").GetValue(o)
I have the following code, where page.Fields is an ExpandoObject. I'm iterating through some user-defined properties and adding them to an Expando, cast to an IDictionary<string,string> to allow me to add new field name / values dynamically, but when I set the Fields property to the value of props, serializing afterwards only gives names with blank values of {}. Why?
page.Fields.Foo = "asdf";
var test = JsonConvert.SerializeObject(page); // shows Foo=asdf in the json
// attach all fields to the page object, casting to an IDictionary to be able to add var names
var props = new ExpandoObject() as IDictionary<string, Object>;
foreach (string key in Request.Form.Keys)
{
if (key.StartsWith("Fields."))
{
var fieldName = key.Substring(key.IndexOf(".") + 1);
props.Add(fieldName, Request.Form[key]);
}
}
var test2 = JsonConvert.SerializeObject(props); // blank values of {}
page.Fields = props as ExpandoObject;
// loses the values for the Fields property
test = JsonConvert.SerializeObject(page);
UPDATE the curse of Nancy strikes, the Request.Form values turned out to be dynamic, so I had to .ToString() it to make it fit into the expected IDictionary<string,string>
To correctly serialize the data you must declare the variable as dynamic, not as an ExpandoObject, JSON .net uses reflection to retrieve properties, if it's a dynamic it casts it as an ExpandoObject and uses the keys as property names, but if you pass the ExpandoObject directly it tries to retrieve the properties from the ExpandoObject type.
Just change
var props = new ExpandoObject() as IDictionary<string, Object>;
to
var props = new ExpandoObject();
var iProps = props as IDictionary<string, Object>;
Use iProps to add the data and pass props to the serialization.
EDIT:
You're storing the value in "Page.Fields", this must be dynamic also.
I'm suspect it's a defect and you are not getting anything for Request.Form.Keys that's matches Field. criteria.
your code works fine for me if I've a page class with dynamic Fields property
dynamic data = JsonConvert.DeserializeObject(result);
foreach (var field in data)
{
var deserializedData = new DeserializedData
{
Label = field.Key.ToObject<string>(),
Content = field.Value.ToObject<string>()
};
}
Note: data = {"name":"test"}
Error:'Newtonsoft.Json.Linq.JProperty' does not contain a definition for 'Key'
I think I understand what the issue is but I am not sure if there is a way to achieve this behavior or is there a different approach to handle the dynamic key value scenarios like this.
The Key will change so I don't know if its going to be name or phone or email or some other field...
In order to iterate through the children of the returned data as an IEnumerable<KeyValuePair<string, JToken>>, you need to explicitly declare the deserialized object as a JObject:
var data = JObject.Parse(result);
foreach (var field in data)
{
var deserializedData = new DeserializedData
{
Label = field.Key,
Content = field.Value.ToObject<string>()
};
}
When you do dynamic data = JsonConvert.DeserializeObject(result);, the foreach ends up calling the IEnumerable<JToken> iterator in the base class JToken. I.e. the following also works:
dynamic data = JsonConvert.DeserializeObject(result);
foreach (var field in data)
{
// "field" happens to be of type JProperty
var deserializedData = new DeserializedData
{
Label = field.Name,
Content = field.Value.ToObject<string>()
};
}
Though I prefer the strongly typed version using JObject since more checking can be done at compile time.
According to the documentation the JProperty's value contains the key, and the JProperty's children are the values. source
I have a large dataset (IEnumerable of [Table]-attributed class objects) from a Linq-To-Sql query and I need to produce a CSV file from it. I loop over the dataset and for each item I convert the value of each property of the item into a string using various formatting options.
Type t = typeof(T);
var properties = t.GetProperties();
foreach (var item in list)
{
foreach (var property in properties)
{
// This is made using delegates, but whatever
object value = property.GetValue(item, null);
// convert to string and feed to StringBuilder
}
}
The problem is that conversion takes even longer that running the query. The dataset contains heavily denormalized data - numerous items have the same properties having the same values and only some properties having different values. Each property value is translated separately for each item in the dataset. So my code converts the same data into the same strings - over and over. And I'd like to somehow speed this up, preferable without changing the SQL query.
Looks like MemoryCache class could work, but I need to craft unique keys for each object. I can't figure out how I could craft such keys reliably and efficiently enough.
How do I make use of MemoryCache so that I can cache translation results for objects of different types?
If you just want to speed it up I would suggest ExpressionTrees more than MemoryCache. This assumes you don't have nested objects to want to read and I can use reflection on the first item and it will be the same for each item in the IEnumerable - which from your example code in the question seems correct.
Also if it's big and you are going to just write it out to a file I would suggest going straight to a FileStream instead of a StringBuilder.
public class CSV
{
public static StringBuilder ToCSV(IEnumerable list)
{
Func<object, object[]> toArray = null;
var sb = new StringBuilder();
// Need to initialize the loop and on the first one grab the properties to setup the columns
foreach (var item in list)
{
if (toArray == null)
{
toArray = ItemToArray(item.GetType());
}
sb.AppendLine(String.Join(",", toArray(item)));
}
return sb;
}
private static Func<object, object[]> ItemToArray(Type type)
{
var props = type.GetProperties().Where(p => p.CanRead);
var arrayBody = new List<Expression>();
// Create a parameter to take the item enumeration
var sourceObject = Expression.Parameter(typeof (object), "source");
// Convert it to the type that is passed in
var sourceParam = Expression.Convert(sourceObject, type);
foreach (var prop in props)
{
var propType = prop.PropertyType;
if (IsValueProperty(propType))
{
// get the value of the property
Expression currentProp = Expression.Property(sourceParam, prop);
// Need to box to an object if value type
if (propType.IsValueType)
{
currentProp = Expression.TypeAs(currentProp, typeof (object));
}
// Add to the collection of expressions so we can build the array off of this collection
arrayBody.Add(currentProp);
}
}
// Create an array based on the properties
var arrayExp = Expression.NewArrayInit(typeof (object), arrayBody);
// set a default return value of null if couldn't match
var defaultValue = Expression.NewArrayInit(typeof (object), Expression.Constant(null));
//Set up so the lambda can have a return value
var returnTarget = Expression.Label(typeof (object[]));
var returnExpress = Expression.Return(returnTarget, arrayExp, typeof (object[]));
var returnLabel = Expression.Label(returnTarget, defaultValue);
//Create the method
var code = Expression.Block(arrayExp, returnExpress, returnLabel);
return Expression.Lambda<Func<object, object[]>>(code, sourceObject).Compile();
}
private static bool IsValueProperty(Type propertyType)
{
var propType = propertyType;
if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof (Nullable<>))
{
propType = new NullableConverter(propType).UnderlyingType;
}
return propType.IsValueType || propType == typeof (string);
}
}
This is for a MVVM based WPF project:
I am using an ExpandoObject in a view model for a dialog, which works very nicely since it implements INotifyPropertyChanged and I can bind to properties of the object directly in XAML.
However, to account for the user manipuating data but then hitting cancel I need to make a copy of the ExpandoObject to restore the original content.
In the dialog no properties are added to the object.
How can I clone it?
Shallow copy:
static ExpandoObject ShallowCopy(ExpandoObject original)
{
var clone = new ExpandoObject();
var _original = (IDictionary<string, object>)original;
var _clone = (IDictionary<string, object>)clone;
foreach (var kvp in _original)
_clone.Add(kvp);
return clone;
}
Deep copy:
static ExpandoObject DeepCopy(ExpandoObject original)
{
var clone = new ExpandoObject();
var _original = (IDictionary<string, object>)original;
var _clone = (IDictionary<string, object>)clone;
foreach (var kvp in _original)
_clone.Add(kvp.Key, kvp.Value is ExpandoObject ? DeepCopy((ExpandoObject)kvp.Value) : kvp.Value);
return clone;
}
As a remaining believer in static typing, eugh...
That being said, it looks like ExpandoObject implements IDictionary<string, object>:
dynamic foo1d = new ExpandoObject();
foo1d.a = "test";
dynamic foo2d = new ExpandoObject();
foreach (var kvp in (IDictionary<string, object>)foo1d)
{
((IDictionary<string, object>)foo2d).Add(kvp);
}
Debug.Assert(foo1d.a == foo2d.a);
or in VB:
Dim foo1d As Object = New ExpandoObject
Dim foo2d As Object = New ExpandoObject
foo1d.a = "foo"
Dim cloneDictionary = CType(foo2d, IDictionary(Of String, Object))
For Each line In CType(foo1d, IDictionary(Of String, Object))
cloneDictionary.Add(line.Key, line.Value)
Next
I will note that this is only a shallow clone. You would have to do further work do clone referenced objects.
var ExpandoObjs = GetDynamicList();
//clone
var clonedExpandos = ExpandoObjs.Cast<dynamic>().Select(x => x).ToList();
//modify and sort
var transformedExpandos = ExpandoObjs.Cast<dynamic>().Select(x =>
{
x.url= x.url + " some more stuff";
return x;
}).OrderBy(x => x.order).ToList();
as long as url is a property of the dynamic object, this will work.
It's useful if you're consuming a service call that doesn't have concrete types and you want to modify/sort the result.