I am working on a project which requires to work with dynamic objects with BreezeSharp. I am receiving the structure of my objects via a JSON file such as:
[{
name: 'Admin',
serviceAddress: 'http://abcd.com/breeze/admins',
dataProperties: {
Id: { type: breeze.DataType.Int32 },
Title: { nullable: true },
ContentTypeId: {},
Created: { type: breeze.DataType.DateTime },
Modified: { type: breeze.DataType.DateTime },
File: { complexType: 'Document:#File' },
},
{
name: 'Car',
serviceAddress: 'http://abcd.com/breeze/cars',
dataProperties: {
Id: { type: breeze.DataType.Int32 },
Model: { nullable: true },
Created: { type: breeze.DataType.DateTime },
Modified: { type: breeze.DataType.DateTime },
}]
and I want to auto generate objects which inherits from Breeze.Sharp.BaseEntity with the properties listed.
I will also have to create the breeze entity manager after the dynamic object has been created.
The reason why I need this is because I have a SharePoint app which uses BreezeJS. The entities are based of a JSON file over there.
So I want to create a Desktop app which would be using the same entities based of the JSON file. I am not sure it is possible to create dynamic objects which inherit the BaseEntity class.
yes, this is possible, but you need to put some work and code into it.
Here is the idea (untested):
Create a class that inherits from BaseEntity and implements IDynamicMetaObjectProvider.
Then you need to build functions that convert your function definitions to dynamic properties:
public class DynamicBreezeEntity : BaseEntity, IDynamicMetaObjectProvider
{
private readonly Dictionary<string, PropertyDefinition> _properties;
public DynamicBreezeEntity ()
{
_properties = new Dictionary<string, PropertyDefinition>();
}
public void DefineProperty(string name, Type type, bool isNullable = false)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException("name");
if (_properties.ContainsKey(name))
throw new ArgumentException("Property already defined.", "name");
if (type == null)
throw new ArgumentNullException("type");
if (isNullable && !type.IsValueType)
throw new ArgumentException("Only value types can be nullable.", "type");
if (isNullable)
{
type = Nullable.GetUnderlyingType(type);
if (type.IsValueType)
type = typeof(Nullable<>).MakeGenericType(type);
}
_properties.Add(name, new PropertyDefinition { Type = type });
}
public object GetValue(string name)
{
PropertyDefinition def;
if (_properties.TryGetValue(name, out def))
return def.Value;
throw new ArgumentException("Property not defined.", "name");
}
public void SetValue(string name, object value)
{
// more work todo here: handle value == null correctly
PropertyDefinition def;
if (_properties.TryGetValue(name, out def) && def.Type.IsAssignableFrom(value.GetType()))
def.Value = value;
throw new ArgumentException("Property not defined.", "name");
}
public IEnumerable<string> GetPropertyNames()
{
return _properties.Keys.ToList();
}
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter)
{
return new Proxy(this);
}
private class PropertyDefinition
{
public Type Type { get; set; }
public object Value { get; set; }
}
private class Proxy : DynamicMetaObject
{
public Proxy(DynamicBreezeEntity host, Expression expression)
: this(host, expression, BindingRestrictions.Empty) { }
public Proxy(DynamicBreezeEntity host, Expression expression, BindingRestrictions restrictions)
: base(expressiom restrictions, host) { }
private DynamicBreezeEntity Host
{
get { return (DynamicBreezeEntity)Value; }
}
private BindingRestrictions GetBindingRestrictions()
{
var restrictions = BindingRestrictions.GetTypeRestriction(this.Expression, this.LimitType);
return restrictions.Merge(BindingRestrictions.GetInstanceRestriction(this.Expression, this.Host));
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return this.Host.GetPropertyNames();
}
public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
{
var arguments = new Expression[] { Expression.Constant(binder.Name) };
var method = typeof(DynamicBreezeEntity).GetMethod("GetValue");
var callExpression = Expression.Convert(Expression.Call(Expression.Convert(this.Expressiom, this.LimitType), method, arguments), binder.ReturnType);
return new DynamicMetaObject(callExpression, GetBindingRestrictions());
}
public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
{
var arguments = new Expression[] {
Expression.Constant(binder.Name),
Expression.Convert(value.Expression, typeof(object))
};
var method = typeof(DynamicBreezeEntity).GetMethod("SetValue");
return new DynamicMetaObject(
Expression.Call(Expression.Convert(this.Expression, this.LimitType), method, arguments),
this.GetBindingRestrictions()
);
}
}
}
I do not know Breeze, BaseEntity may has abstract properties/methods that you need to implement. I did not focus on that.
You can use now as
var breeze = new DynamicBreezeEntity();
breeze.DefineProperty("Id", typeof(Int32));
dynamic dynBreeze = breeze;
dynBreeze.Id = "Foo";
Console.WriteLine("Id: {0}", dynBreeze.Id);
Code is probably not complete (have no VS access at the moment), but should point you in the right direction.
Related
NOTE: I am using Microsoft's new System.Text.Json and not Json.NET so make sure answers address this accordingly.
Consider these simple POCOs:
interface Vehicle {}
class Car : Vehicle {
string make { get; set; }
int numberOfDoors { get; set; }
}
class Bicycle : Vehicle {
int frontGears { get; set; }
int backGears { get; set; }
}
The car can be represented in JSON like this...
{
"make": "Smart",
"numberOfDoors": 2
}
and the bicycle can be represented like this...
{
"frontGears": 3,
"backGears": 6
}
Pretty straight forward. Now consider this JSON.
[
{
"Car": {
"make": "Smart",
"numberOfDoors": 2
}
},
{
"Car": {
"make": "Lexus",
"numberOfDoors": 4
}
},
{
"Bicycle" : {
"frontGears": 3,
"backGears": 6
}
}
]
This is an array of objects where the property name is the key to know which type the corresponding nested object refers to.
While I know how to write a custom converter that uses the UTF8JsonReader to read the property names (e.g. 'Car' and 'Bicycle' and can write a switch statement accordingly, what I don't know is how to fall back to the default Car and Bicycle converters (i.e. the standard JSON converters) since I don't see any method on the reader to read in a specific typed object.
So how can you manually deserialize nested objects like this?
I figured it out. You simply pass your reader/writer down to another instance of the JsonSerializer and it handles it as if it were a native object.
Here's a complete example you can paste into something like RoslynPad and just run it.
Here's the implementation...
using System;
using System.Collections.ObjectModel;
using System.Text.Json;
using System.Text.Json.Serialization;
public class HeterogenousListConverter<TItem, TList> : JsonConverter<TList>
where TItem : notnull
where TList : IList<TItem>, new() {
public HeterogenousListConverter(params (string key, Type type)[] mappings){
foreach(var (key, type) in mappings)
KeyTypeLookup.Add(key, type);
}
public ReversibleLookup<string, Type> KeyTypeLookup = new ReversibleLookup<string, Type>();
public override bool CanConvert(Type typeToConvert)
=> typeof(TList).IsAssignableFrom(typeToConvert);
public override TList Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options){
// Helper function for validating where you are in the JSON
void validateToken(Utf8JsonReader reader, JsonTokenType tokenType){
if(reader.TokenType != tokenType)
throw new JsonException($"Invalid token: Was expecting a '{tokenType}' token but received a '{reader.TokenType}' token");
}
validateToken(reader, JsonTokenType.StartArray);
var results = new TList();
reader.Read(); // Advance to the first object after the StartArray token. This should be either a StartObject token, or the EndArray token. Anything else is invalid.
while(reader.TokenType == JsonTokenType.StartObject){ // Start of 'wrapper' object
reader.Read(); // Move to property name
validateToken(reader, JsonTokenType.PropertyName);
var typeKey = reader.GetString();
reader.Read(); // Move to start of object (stored in this property)
validateToken(reader, JsonTokenType.StartObject); // Start of vehicle
if(KeyTypeLookup.TryGetValue(typeKey, out var concreteItemType)){
var item = (TItem)JsonSerializer.Deserialize(ref reader, concreteItemType, options);
results.Add(item);
}
else{
throw new JsonException($"Unknown type key '{typeKey}' found");
}
reader.Read(); // Move past end of item object
reader.Read(); // Move past end of 'wrapper' object
}
validateToken(reader, JsonTokenType.EndArray);
return results;
}
public override void Write(Utf8JsonWriter writer, TList items, JsonSerializerOptions options){
writer.WriteStartArray();
foreach (var item in items){
var itemType = item.GetType();
writer.WriteStartObject();
if(KeyTypeLookup.ReverseLookup.TryGetValue(itemType, out var typeKey)){
writer.WritePropertyName(typeKey);
JsonSerializer.Serialize(writer, item, itemType, options);
}
else{
throw new JsonException($"Unknown type '{itemType.FullName}' found");
}
writer.WriteEndObject();
}
writer.WriteEndArray();
}
}
Here's the demo code...
#nullable disable
public interface IVehicle { }
public class Car : IVehicle {
public string make { get; set; } = null;
public int numberOfDoors { get; set; } = 0;
public override string ToString()
=> $"{make} with {numberOfDoors} doors";
}
public class Bicycle : IVehicle{
public int frontGears { get; set; } = 0;
public int backGears { get; set; } = 0;
public override string ToString()
=> $"{nameof(Bicycle)} with {frontGears * backGears} gears";
}
string json = #"[
{
""Car"": {
""make"": ""Smart"",
""numberOfDoors"": 2
}
},
{
""Car"": {
""make"": ""Lexus"",
""numberOfDoors"": 4
}
},
{
""Bicycle"": {
""frontGears"": 3,
""backGears"": 6
}
}
]";
var converter = new HeterogenousListConverter<IVehicle, ObservableCollection<IVehicle>>(
(nameof(Car), typeof(Car)),
(nameof(Bicycle), typeof(Bicycle))
);
var options = new JsonSerializerOptions();
options.Converters.Add(converter);
var vehicles = JsonSerializer.Deserialize<ObservableCollection<IVehicle>>(json, options);
Console.Write($"{vehicles.Count} Vehicles: {String.Join(", ", vehicles.Select(v => v.ToString())) }");
var json2 = JsonSerializer.Serialize(vehicles, options);
Console.WriteLine(json2);
Console.WriteLine($"Completed at {DateTime.Now}");
Here's the supporting two-way lookup used above...
using System.Collections.ObjectModel;
using System.Diagnostics;
public class ReversibleLookup<T1, T2> : ReadOnlyDictionary<T1, T2>
where T1 : notnull
where T2 : notnull {
public ReversibleLookup(params (T1, T2)[] mappings)
: base(new Dictionary<T1, T2>()){
ReverseLookup = new ReadOnlyDictionary<T2, T1>(reverseLookup);
foreach(var mapping in mappings)
Add(mapping.Item1, mapping.Item2);
}
private readonly Dictionary<T2, T1> reverseLookup = new Dictionary<T2, T1>();
public ReadOnlyDictionary<T2, T1> ReverseLookup { get; }
[DebuggerHidden]
public void Add(T1 value1, T2 value2) {
if(ContainsKey(value1))
throw new InvalidOperationException($"{nameof(value1)} is not unique");
if(ReverseLookup.ContainsKey(value2))
throw new InvalidOperationException($"{nameof(value2)} is not unique");
Dictionary.Add(value1, value2);
reverseLookup.Add(value2, value1);
}
public void Clear(){
Dictionary.Clear();
reverseLookup.Clear();
}
}
Here is another solution that builds upon the previous ones (with slightly different JSON structure).
Notable differences:
Discriminator is part of the object (no need to use wrapper objects)
To my own surprise, it is not necessary to remove the converter with recursive (de)serialize calls (.NET 6)
I didn't add custom lookup, see previous answers
The code:
var foo = new[] {
new Foo
{
Inner = new Bar
{
Value = 42,
},
},
new Foo
{
Inner = new Baz
{
Value = "Hello",
},
},
};
var opts = new JsonSerializerOptions
{
Converters =
{
new PolymorphicJsonConverterWithDiscriminator<Base>(typeof(Bar), typeof(Baz)),
},
};
var json = JsonSerializer.Serialize(foo, opts);
var foo2 = JsonSerializer.Deserialize<Foo[]>(json, opts);
Console.WriteLine(foo2 is not null && foo2.SequenceEqual(foo));
Console.ReadLine();
public static class Constants
{
public const string DiscriminatorPropertyName = "$type";
}
public record Foo
{
public Base? Inner { get; set; }
}
public abstract record Base();
public record Bar : Base
{
[JsonPropertyName(DiscriminatorPropertyName)]
[JsonPropertyOrder(int.MinValue)]
public string TypeDiscriminator { get => nameof(Bar); init { if (value != nameof(Bar)) throw new ArgumentException(); } }
public int Value { get; set; }
}
public record Baz : Base
{
[JsonPropertyName(DiscriminatorPropertyName)]
[JsonPropertyOrder(int.MinValue)]
public string TypeDiscriminator { get => nameof(Baz); init { if (value != nameof(Baz)) throw new ArgumentException(); } }
public string? Value { get; set; }
}
public class PolymorphicJsonConverterWithDiscriminator<TBase> : JsonConverter<TBase>
where TBase : class
{
private readonly Type[] supportedTypes;
public PolymorphicJsonConverterWithDiscriminator(params Type[] supportedTypes)
{
this.supportedTypes = supportedTypes;
}
public override TBase? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Clone the reader so we can pass the original to Deserialize.
var readerClone = reader;
if (readerClone.TokenType == JsonTokenType.Null)
{
return null;
}
if (readerClone.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
var propertyName = readerClone.GetString();
if (propertyName != DiscriminatorPropertyName)
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.String)
{
throw new JsonException();
}
var typeIdentifier = readerClone.GetString();
var specificType = supportedTypes.FirstOrDefault(t => t.Name == typeIdentifier)
?? throw new JsonException();
return (TBase?)JsonSerializer.Deserialize(ref reader, specificType, options);
}
public override void Write(Utf8JsonWriter writer, TBase value, JsonSerializerOptions options)
{
// Cast to object which forces the serializer to use runtime type.
JsonSerializer.Serialize(writer, value, typeof(object), options);
}
}
Sample JSON:
[
{
"Inner": {
"$type": "Bar",
"Value": 42
}
},
{
"Inner": {
"$type": "Baz",
"Value": "Hello"
}
}
]
Here is a solution that works with single objects (does not require an array of objects). This is a copy of https://stackoverflow.com/a/59744873 modified to work without IList.
This is the main class
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Shared.DataAccess
{
/// <summary>
/// Enables System.Text.Json to handle polymorphic classes
/// The polymorphic classes must be explicitly mapped
/// </summary>
/// <example>
/// Mapping
/// TradeStrategy (base) to
/// TradeStrategyNone and TradeStrategyRandom (derived)
///
/// var converter = new JsonPolymorphicConverter<TradeStrategy>(
/// (nameof(TradeStrategyNone), typeof(TradeStrategyNone)),
/// (nameof(TradeStrategyRandom), typeof(TradeStrategyRandom)));
/// var options = new JsonSerializerOptions();
/// var options.Converters.Add(converter);
/// </example>
/// <typeparam name="TItem">Base class type</typeparam>
public class JsonPolymorphicConverter<TItem> : JsonConverter<TItem>
where TItem : notnull
{
public JsonPolymorphicConverter(params (string key, Type type)[] mappings)
{
foreach (var (key, type) in mappings)
KeyTypeLookup.Add(key, type);
}
public ReversibleLookup<string, Type> KeyTypeLookup = new ReversibleLookup<string, Type>();
public override bool CanConvert(Type typeToConvert)
=> typeof(TItem).IsAssignableFrom(typeToConvert);
public override TItem Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// Helper function for validating where you are in the JSON
void validateToken(Utf8JsonReader reader, JsonTokenType tokenType)
{
if (reader.TokenType != tokenType)
throw new JsonException($"Invalid token: Was expecting a '{tokenType}' token but received a '{reader.TokenType}' token");
}
TItem result = default(TItem);
reader.Read(); // Move to property name
validateToken(reader, JsonTokenType.PropertyName);
var typeKey = reader.GetString();
reader.Read(); // Move to start of object (stored in this property)
validateToken(reader, JsonTokenType.StartObject); // Start of vehicle
if (KeyTypeLookup.TryGetValue(typeKey, out var concreteItemType))
{
// WORKAROUND - stop cyclic look up
// If we leave our converter in the options then will get infinite cycling
// We create a temp options with our converter removed to stop the cycle
JsonSerializerOptions tempOptions = new JsonSerializerOptions(options);
tempOptions.Converters.Remove(this);
// Use normal deserialization
result = (TItem)JsonSerializer.Deserialize(ref reader, concreteItemType, tempOptions);
}
else
{
throw new JsonException($"Unknown type key '{typeKey}' found");
}
reader.Read(); // Move past end of item object
return result;
}
public override void Write(Utf8JsonWriter writer, TItem item, JsonSerializerOptions options)
{
var itemType = item.GetType();
writer.WriteStartObject();
if (KeyTypeLookup.ReverseLookup.TryGetValue(itemType, out var typeKey))
{
writer.WritePropertyName(typeKey);
// WORKAROUND - stop cyclic look up
// If we leave our converter in the options then will get infinite cycling
// We create a temp options with our converter removed to stop the cycle
JsonSerializerOptions tempOptions = new JsonSerializerOptions(options);
tempOptions.Converters.Remove(this);
// Use normal serialization
JsonSerializer.Serialize(writer, item, itemType, tempOptions);
}
else
{
throw new JsonException($"Unknown type '{itemType.FullName}' found");
}
writer.WriteEndObject();
}
}
}
This also relies on the ReversibleLookup class from https://stackoverflow.com/a/59744873. I am copying here for convenience. The code is the same, I've only added a comment at the top.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
namespace Shared.DataAccess
{
/// <summary>
/// Helper class used with JsonPolymorphicConverter and HeterogenousListConverter
/// </summary>
/// <typeparam name="T1">First class type</typeparam>
/// <typeparam name="T2">Second class type</typeparam>
public class ReversibleLookup<T1, T2> : ReadOnlyDictionary<T1, T2>
where T1 : notnull
where T2 : notnull
{
public ReversibleLookup(params (T1, T2)[] mappings)
: base(new Dictionary<T1, T2>())
{
ReverseLookup = new ReadOnlyDictionary<T2, T1>(reverseLookup);
foreach (var mapping in mappings)
Add(mapping.Item1, mapping.Item2);
}
private readonly Dictionary<T2, T1> reverseLookup = new Dictionary<T2, T1>();
public ReadOnlyDictionary<T2, T1> ReverseLookup { get; }
[DebuggerHidden]
public void Add(T1 value1, T2 value2)
{
if (ContainsKey(value1))
throw new InvalidOperationException($"{nameof(value1)} is not unique");
if (ReverseLookup.ContainsKey(value2))
throw new InvalidOperationException($"{nameof(value2)} is not unique");
Dictionary.Add(value1, value2);
reverseLookup.Add(value2, value1);
}
public void Clear()
{
Dictionary.Clear();
reverseLookup.Clear();
}
}
}
Example usage
public class TradeStrategy {
public string name;
public TradeStrategy() : this("Unknown") { }
public TradeStrategy(string name) { this.name = name; }
public virtual double CalcAdjustments(double stockPrice) => 0.0;
}
public class TradeStrategyNone : TradeStrategy {
public TradeStrategyNone() : base("None") { }
public override double CalcAdjustments(double stockPrice) => 0.0;
}
public class TradeStrategyRandom : TradeStrategy {
private Random random { get; set; }
public TradeStrategyRandom() : base("Random") { random = new Random(); }
public override double CalcAdjustments(double stockPrice) => random.NextDouble();
}
public class Portfolio {
public TradeStrategy strategy;
}
var converter = new JsonPolymorphicConverter<TradeStrategy>(
(nameof(TradeStrategyNone), typeof(TradeStrategyNone)),
(nameof(TradeStrategyRandom), typeof(TradeStrategyRandom)));
var options = new JsonSerializerOptions();
options.Converters.Add(converter);
Portfolio port1 = new Portfolio();
port1.strategy = new TradeStrategyRandom();
// port1Json will contain "TradeStrategyRandom" type info for "TradeStrategy" strategy variable
var port1Json = JsonSerializer.Serialize(port1, options);
// port1Copy will properly create "TradeStrategyRandom" from the port1Json
Portfolio port1Copy = JsonSerializer.Deserialize<Portfolio>(port1Json, options);
// Without "options" the JSON will end up stripping down TradeStrategyRandom to TradeStrategy
If you are trying to figure out the difference between this solution and the other one, know that the other solution requires you to create an array of the item you want to convert. This solution will work with a single object.
Here's a simplistic approach that I hope works for you.
You can use a dynamic variable
I noticed in the comments that you weren't keen to use NetwonSoft.Json, you could use this code: dynamic car = Json.Decode(json);
The Json class comes from here
Say, I have these properties in a class :
public class Example
{
public List<Toyota> listToyota;
public List<Honda> listHonda;
public List<Huyndai> listHuyndai;
...
...
}
And there will be more properties if there are new car brands. Each brand is a table.
Normally, I would do this to get the data from tables :
Example result = new Example();
switch (brandname)
{
case "Toyota":
result.listToyota = * select table from context * ;
break;
case "Honda":
result.listHonda = * select table from context * ;
break;
...
}
Also, I'll have to add more code when there are new brands. I found this very annoying/time-consuming and decided to switch to a dynamic approach. I've sucessfully get the tables dynamically :
tblName = "Toyota";
IEnumerable<dynamic> table = typeof(MyContext).GetProperty(tblName).GetValue(context, null) as IEnumerable<dynamic>;
But I failed to dynamically set the property value, in this example, is listToyota :
query = (from a in table select a).ToList() as List<dynamic>;
SetPropertyValue(result, "listToyota", query);
I got this error :
Object of type 'System.Collections.Generic.List1[System.Object]'
cannot be converted to type
'System.Collections.Generic.List1[Toyota]'.
SetPropertyValue is a very simple function using System.Reflection :
static void SetPropertyValue(object p, string propName, object value)
{
Type t = p.GetType();
System.Reflection.PropertyInfo info = t.GetProperty(propName);
if (info == null)
return;
if (!info.CanWrite)
return;
info.SetValue(p, value, null);
}
Any advices are greatly appreciated!
Please try something like this
static void SetPropertyValue(object p, string propName, object value)
{
Type t = p.GetType();
System.Reflection.PropertyInfo info = t.GetProperty(propName);
if (info == null)
return;
if (!info.CanWrite)
return;
var elemtype = info.PropertyType.GetElementType();
var castmethod = typeof(Enumerable).GetMethod("Cast", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(elemtype);
var tolist = typeof(Enumerable).GetMethod("ToList", BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(elemtype);
var collection = castmethod.Invoke(null, new object[] { value });
var list = tolist.Invoke(null, new object[] { collection });
info.SetValue(p, list, null);
}
I think the main problem is your software design. I wouldn't use dynamic this way.
I would create a Brand-Class. If you want to use dynamic, try something like this:
public class DynamicBrand : DynamicObject
{
private IDictionary<string, object> myDynamicValues;
public DynamicBrand()
: base()
{
myDynamicValues = new Dictionary<string, object>();
}
public void AddMember(string Name, object Value)
{
if (!myDynamicValues.ContainsKey(Name.Trim().ToLower()))
{
myDynamicValues.Add(Name.ToLower().Trim(), Value);
}
else
{
throw new Exception("The Member with the Name: " + Name + " already exists!");
}
}
public override bool TrySetMember(SetMemberBinder Binder, object Value)
{
if (myDynamicValues.ContainsKey(Binder.Name.ToLower()))
{
myDynamicValues[Binder.Name.ToLower()] = Value;
return true;
}
else
{
myDynamicValues.Add(Binder.Name.ToLower(), Value);
}
return true;
}
publc override bool TryGetMember(GetMemberBinder Binder, out object Result)
{
if (myDynamicValues.ContainsKey(Binder.Name.ToLower()))
{
Result = myDynamicValues[Binder.Name.ToLower()];
return true;
}
else
{
Result = null;
return false;
}
}
I would change your example class
public class Example
{
// string = brand-name; list = list of dynamic brand items
public Dictionary<string, List<DynamicBrand>> brands;
}
Whenyou fill your data, just add a new dynamic brand to your brands-list and simply add the member you need.
EDIT: Dynamic isn't a very nice solution for your problem. I would think about a completly different structure.
I try to create an object model for the following problem.
I need a folder object (comparable to directory folders). Each folder can contain additional sub folders and in addition parameter objects (comparable to files). In addition, each parameter needs to know in which folder it resides. This is easy so far. So I implemented the following working solution.
I have a base object, that can either be inherited to a folder or a parameter:
[Serializable()]
public class Entry
{
public Func<string> GetPath;
public string Path
{
get
{
if (GetPath == null) return string.Empty;
return GetPath.Invoke();
}
}
}
Now I created a FolderEntry, that inherits from Entry and supports adding new sub entries by implementing IList<>.
[Serializable()]
class FolderEntry : Entry, IList<Entry>
{
private readonly List<Entry> _entries;
public FolderEntry()
{
_entries = new List<Entry>();
}
public string FolderName { get; set; }
private void SetPathDelegate(Entry entry)
{
if (entry.GetPath != null) throw new ArgumentException("entry already assigned");
entry.GetPath = () =>
{
if (GetPath == null || string.IsNullOrEmpty(GetPath.Invoke())) return FolderName;
return GetPath.Invoke() + "|" + FolderName;
};
}
public void Add(Entry item)
{
SetPathDelegate(item);
_entries.Add(item);
}
[...]
}
To support Undo/Redo functionality, I made all classes serializable by adding the Serializable-Attribute.
This serialization is working so far using the following test:
var folderA = new FolderEntry();
var folderB = new FolderEntry();
folderA.Add(folderB);
var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
var memStream = new System.IO.MemoryStream();
serializer.Serialize(memStream, folderA);
Now here’s my problem. There is in addition the need that each parameter knows its index inside the hosting list. I changed my Entry-object to have a property Index and a delegate GetIndex in the same manner as Path and GetPath before:
[Serializable()]
public class Entry
{
public Func<string> GetPath;
public string Path
{
get
{
if (GetPath == null) return string.Empty;
return GetPath.Invoke();
}
}
public Func<int> GetIndex;
public int Index
{
get
{
if (GetIndex == null) return -1;
return GetIndex.Invoke();
}
}
}
Inside the SetPathDelegate of the Folder-object I assigned the new delegate
private void SetPathDelegate(Entry entry)
{
if (entry.GetPath != null) throw new ArgumentException("entry already assigned");
if (entry.GetIndex != null) throw new ArgumentException("entry already assigned");
entry.GetPath = () =>
{
if (GetPath == null || string.IsNullOrEmpty(GetPath.Invoke())) return FolderName;
return GetPath.Invoke() + "|" + FolderName;
};
entry.GetIndex = () =>
{
return _entries.IndexOf(entry);
};
}
If I try to serialize this, I get an expection that my „FolderEntry+<>c__DisplayClass2“ in Assembly… is not marked as serializable. I can’t see an obvious difference between GetPath and GetIndex. To narrow it down, I replaced content of the created GetIndex delegate in SetPathDelegate from
entry.GetIndex = () =>
{
return _entries.IndexOf(entry);
};
To
entry.GetIndex = () =>
{
return -1;
};
To my astonishment this is serializable again. Why doesn‘t cause my GetPath delegate any problems regarding the serialization but my GetIndex delegate does?
The problem is the anonymous function that you assign to GetIndex. At runtime, a new type is created, which is not marked as serializable.
According to this post, you should set a SurrogateSelector for the formatter (with some caveats, read the article in detail):
formatter.SurrogateSelector = new UnattributedTypeSurrogateSelector();
I'me pasting here the classes from the article, for future reference and in order to make the answer thorough.
public class UnattributedTypeSurrogate : ISerializationSurrogate
{
private const BindingFlags publicOrNonPublicInstanceFields =
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
public void GetObjectData(object obj,
SerializationInfo info, StreamingContext context)
{
var type = obj.GetType();
foreach (var field in type.GetFields(publicOrNonPublicInstanceFields))
{
var fieldValue = field.GetValue(obj);
var fieldValueIsNull = fieldValue != null;
if (fieldValueIsNull)
{
var fieldValueRuntimeType = fieldValue.GetType();
info.AddValue(field.Name + "RuntimeType",
fieldValueRuntimeType.AssemblyQualifiedName);
}
info.AddValue(field.Name + "ValueIsNull", fieldValueIsNull);
info.AddValue(field.Name, fieldValue);
}
}
public object SetObjectData(object obj,
SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
var type = obj.GetType();
foreach (var field in type.GetFields(publicOrNonPublicInstanceFields))
{
var fieldValueIsSerializable = info.GetBoolean(field.Name + "ValueIsNull");
if (fieldValueIsSerializable)
{
var fieldValueRuntimeType = info.GetString(field.Name + "RuntimeType");
field.SetValue(obj,
info.GetValue(field.Name, Type.GetType(fieldValueRuntimeType)));
}
}
return obj;
}
}
public class UnattributedTypeSurrogateSelector : ISurrogateSelector
{
private readonly SurrogateSelector innerSelector = new SurrogateSelector();
private readonly Type iFormatter = typeof(IFormatter);
public void ChainSelector(ISurrogateSelector selector)
{
innerSelector.ChainSelector(selector);
}
public ISerializationSurrogate GetSurrogate(
Type type, StreamingContext context, out ISurrogateSelector selector)
{
if (!type.IsSerializable)
{
selector = this;
return new UnattributedTypeSurrogate();
}
return innerSelector.GetSurrogate(type, context, out selector);
}
public ISurrogateSelector GetNextSelector()
{
return innerSelector.GetNextSelector();
}
}
I'm talking about something similar to dynamic. This didn't answer my question, hence this question. I want to have a class that I can add properties to at runtime. It needs to be inherited from the type object.
I've seen inheriting from DynamicObject, but it didn't state how to add properties at run-time. Could some light be shed on this for me pls?
I have a class like this:
public class SomeModel : DynamicObject {
public string SomeMandatoryProperty {get; set;}
}
I'd like to add all properties from another class to this class at runtime. So eg.
SomeModel m = new SomeModel();
m = someOtherClass;
string hi = m.otherClassProp; //Property from other class is added.
string mandatory = m.SomeMandatoryProperty; //From the mandatory property set previously.
I think you are looking for ExpandoObject:
The ExpandoObject class enables you to
add and delete members of its
instances at run time and also to set
and get values of these members. This
class supports dynamic binding, which
enables you to use standard syntax
like sampleObject.sampleMember instead
of more complex syntax like
sampleObject.GetAttribute("sampleMember").
dynamic manager;
manager = new ExpandoObject();
manager.Name = "Allison Brown";
manager.Age = 42;
manager.TeamSize = 10;
You should be able to make use of ExpandoObject instead. An ExpandoObject can have members added or removed at runtime and has very nice support if you want to convert to XML etc.
From MSDN Documentation:
dynamic employee, manager;
employee = new ExpandoObject();
employee.Name = "John Smith";
employee.Age = 33;
manager = new ExpandoObject();
manager.Name = "Allison Brown";
manager.Age = 42;
manager.TeamSize = 10;
You'd want to use the ExpandoObject as you can dynamically add properties as needed. There isn't however a direct way to populate an instance with the values from another object easily. You'll have to add it manually using reflection.
Do you want to write a wrapper object where you could add properties to while still accessing the inner? You may want to consider it that way you don't have to manage two copies of values between two different object instances. I wrote a test class to wrap string objects to demonstrate how you can do this (similar to how the ExpandoObject works). It should give you an idea on how you can do this for your types.
class DynamicString : DynamicObject
{
static readonly Type strType = typeof(string);
private string instance;
private Dictionary<string, object> dynProperties;
public DynamicString(string instance)
{
this.instance = instance;
dynProperties = new Dictionary<string, object>();
}
public string GetPrefixString(string prefix)
{
return String.Concat(prefix, instance);
}
public string GetSuffixString(string suffix)
{
return String.Concat(instance, suffix);
}
public override string ToString()
{
return instance;
}
public override bool TryConvert(ConvertBinder binder, out object result)
{
if (binder.Type != typeof(string))
return base.TryConvert(binder, out result);
result = instance;
return true;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
var method = strType.GetMethod(binder.Name, args.Select(a => a.GetType()).ToArray());
if (method == null)
{
result = null;
return false;
}
result = method.Invoke(instance, args);
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var members = strType.GetMember(binder.Name);
if (members.Length > 0)
{
var member = members.Single();
switch (member.MemberType)
{
case MemberTypes.Property:
result = ((PropertyInfo)member).GetValue(instance, null);
return true;
break;
case MemberTypes.Field:
result = ((FieldInfo)member).GetValue(instance);
return true;
break;
}
}
return dynProperties.TryGetValue(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
var ret = base.TrySetMember(binder, value);
if (ret) return true;
dynProperties[binder.Name] = value;
return true;
}
}
If you want to be adventurous, you can define your own meta objects to handle the bindings. You could end up with reusable meta objects for different types and simplify your code immensely. I've been playing with this for a while and have this so far. It doesn't handle dynamically adding properties yet. I won't be working on this any further but I'll just leave it here for reference.
class DynamicString : DynamicObject
{
class DynamicStringMetaObject : DynamicMetaObject
{
public DynamicStringMetaObject(Expression parameter, object value)
: base(parameter, BindingRestrictions.Empty, value)
{
}
public override DynamicMetaObject BindConvert(ConvertBinder binder)
{
if (binder.Type == typeof(string))
{
var valueType = Value.GetType();
return new DynamicMetaObject(
Expression.MakeMemberAccess(
Expression.Convert(Expression, valueType),
valueType.GetProperty("Instance")),
BindingRestrictions.GetTypeRestriction(Expression, valueType));
}
return base.BindConvert(binder);
}
public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
{
System.Diagnostics.Trace.WriteLine(String.Format("BindGetMember: {0}", binder.Name));
var valueType = Value.GetType();
var self = Expression.Convert(Expression, valueType);
var valueMembers = valueType.GetMember(binder.Name);
if (valueMembers.Length > 0)
{
return BindGetMember(self, valueMembers.Single());
}
var members = typeof(string).GetMember(binder.Name);
if (members.Length > 0)
{
var instance =
Expression.MakeMemberAccess(
self,
valueType.GetProperty("Instance"));
return BindGetMember(instance, members.Single());
}
return base.BindGetMember(binder);
}
private DynamicMetaObject BindGetMember(Expression instance, MemberInfo member)
{
return new DynamicMetaObject(
Expression.Convert(
Expression.MakeMemberAccess(instance, member),
typeof(object)),
BindingRestrictions.GetTypeRestriction(Expression, Value.GetType())
);
}
}
public string Instance { get; private set; }
public DynamicString(string instance)
{
Instance = instance;
}
public override DynamicMetaObject GetMetaObject(Expression parameter)
{
return new DynamicStringMetaObject(parameter, this);
}
public override string ToString()
{
return Instance;
}
public string GetPrefixString(string prefix)
{
return String.Concat(prefix, Instance);
}
public string GetSuffixString(string suffix)
{
return String.Concat(Instance, suffix);
}
}
for auditory reasons I stores the arguments of the business methods serialized into the database using the binaryformatter.
The problem is that when an argument is a generic list I don't find the way to cast the deserialized object because I don't know the type, or If I will know the type I don't know how to cast the object at runtime.
Anybody knows how to cast an object containing a generic list dinamically at runtime?
I need to do this because I need to show the deserialized data in a property grid:
object objArg = bformatter.Deserialize(memStr);
//If the type is a clr type (int, string, etc)
if (objArg.GetType().Module.Name == "mscorlib.dll")
{
//If the type is a generic type (List<>, etc)
//(I'm only use List for these cases)
if (objArg.GetType().IsGenericType)
{
// here is the problem
pgArgsIn.SelectedObject = new { Value = objArg};
//In the previous line I need to do something like...
//new { Value = (List<objArg.GetYpe()>) objArg};
}
else
{
pgArgsIn.SelectedObject = new { Value = objArg.ToString() };
}
}
else
{
//An entity object
pgArgsIn.SelectedObject = objArg;
}
With BinaryFormatter you don't need to know the type; the metadata is included in the stream (making it bigger, but hey!). However, you can't cast unless you know the type. Often in this scenario you have to use common known interfaces (non-generic IList etc) and reflection. And lots of it.
I also can't think of a huge requirement to know the type to show in a PropertyGrid - since this accepts object, just give it what BinaryFormatter provides. Is there a specific issue you are seeing there? Again, you might want to check for IList (non-generic) - but it isn't worth worrying about IList<T>, since this isn't what PropertyGrid checks for!
You can of course find the T if you want (like so) - and use MakeGenericType() and Activator.CreateInstance - not pretty.
OK; here's a way using custom descriptors that doesn't involve knowing anything about the object or the list type; if you really want it is possible to expand the list items directly into the properties, so in this example you'd see 2 fake properties ("Fred" and "Wilma") - that is extra work, though ;-p
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
class Person
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public override string ToString() {
return Name;
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Person fred = new Person();
fred.Name = "Fred";
fred.DateOfBirth = DateTime.Today.AddYears(-23);
Person wilma = new Person();
wilma.Name = "Wilma";
wilma.DateOfBirth = DateTime.Today.AddYears(-20);
ShowUnknownObject(fred, "Single object");
List<Person> list = new List<Person>();
list.Add(fred);
list.Add(wilma);
ShowUnknownObject(list, "List");
}
static void ShowUnknownObject(object obj, string caption)
{
using(Form form = new Form())
using (PropertyGrid grid = new PropertyGrid())
{
form.Text = caption;
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
grid.SelectedObject = ListWrapper.Wrap(obj);
Application.Run(form);
}
}
}
[TypeConverter(typeof(ListWrapperConverter))]
public class ListWrapper
{
public static object Wrap(object obj)
{
IListSource ls = obj as IListSource;
if (ls != null) obj = ls.GetList(); // list expansions
IList list = obj as IList;
return list == null ? obj : new ListWrapper(list);
}
private readonly IList list;
private ListWrapper(IList list)
{
if (list == null) throw new ArgumentNullException("list");
this.list = list;
}
internal class ListWrapperConverter : TypeConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(
ITypeDescriptorContext context, object value, Attribute[] attributes) {
return new PropertyDescriptorCollection(
new PropertyDescriptor[] { new ListWrapperDescriptor(value as ListWrapper) });
}
}
internal class ListWrapperDescriptor : PropertyDescriptor {
private readonly ListWrapper wrapper;
internal ListWrapperDescriptor(ListWrapper wrapper) : base("Wrapper", null)
{
if (wrapper == null) throw new ArgumentNullException("wrapper");
this.wrapper = wrapper;
}
public override bool ShouldSerializeValue(object component) { return false; }
public override void ResetValue(object component) {
throw new NotSupportedException();
}
public override bool CanResetValue(object component) { return false; }
public override bool IsReadOnly {get {return true;}}
public override void SetValue(object component, object value) {
throw new NotSupportedException();
}
public override object GetValue(object component) {
return ((ListWrapper)component).list;
}
public override Type ComponentType {
get { return typeof(ListWrapper); }
}
public override Type PropertyType {
get { return wrapper.list.GetType(); }
}
public override string DisplayName {
get {
IList list = wrapper.list;
if (list.Count == 0) return "Empty list";
return "List of " + list.Count
+ " " + list[0].GetType().Name;
}
}
}
}
If the serializer you are using does not retain the type - at the least, you must store the type of T along with the data, and use that to create the generic list reflectively:
//during storage:
Type elementType = myList.GetType().GetGenericTypeDefinition().GetGenericArguments[0];
string typeNameToSave = elementType.FullName;
//during retrieval
string typeNameFromDatabase = GetTypeNameFromDB();
Type elementType = Type.GetType(typeNameFromDatabase);
Type listType = typeof(List<>).MakeGenericType(new Type[] { elementType });
Now you have listType, which is the exact List<T> you used (say, List<Foo>). You can pass that type into your deserialization routine.