I am aware that the classic way to create a dynamic object is to inherit from DynamicObject. However if I already have a class and I wish to add dynamic properties to subclasses of that then I am stuck.
Say I have a class ReactiveObject And I wish to add dynamic properties to it using DynamicObject. So I do this
public class MyReactiveObject : ReactiveObject, IDynamicMetaObjectProvider{
public DynamicMetaObject GetMetaObject(Expression parameter)
{
...
}
}
I thought the easy way to do this might be to create an instance of DynamicObject and proxy the call to that.
public class MyDynamicObject : DynamicObject{}
public class MyReactiveObject : ReactiveObject, IDynamicMetaObjectProvider{
MyDynamicObject DynamicObject = new MyDynamicObject();
public DynamicMetaObject GetMetaObject(Expression parameter)
{
return this.DynamicObject.GetMetaObject(parameter);
}
}
except that is not going to work because the returned meta object doesn't know anything about the methods on MyReactiveObject. Is there any easy way to do this without fully reimplementing DynamicObject.
I came across the following gist.
https://gist.github.com/breezhang/8954586
public sealed class ForwardingMetaObject : DynamicMetaObject
{
private readonly DynamicMetaObject _metaForwardee;
public ForwardingMetaObject(
Expression expression,
BindingRestrictions restrictions,
object forwarder,
IDynamicMetaObjectProvider forwardee,
Func<Expression, Expression> forwardeeGetter
) : base(expression, restrictions, forwarder)
{
// We'll use forwardee's meta-object to bind dynamic operations.
_metaForwardee = forwardee.GetMetaObject(
forwardeeGetter(
Expression.Convert(expression, forwarder.GetType()) // [1]
)
);
}
// Restricts the target object's type to TForwarder.
// The meta-object we are forwarding to assumes that it gets an instance of TForwarder (see [1]).
// We need to ensure that the assumption holds.
private DynamicMetaObject AddRestrictions(DynamicMetaObject result)
{
var restricted = new DynamicMetaObject(
result.Expression,
BindingRestrictions.GetTypeRestriction(Expression, Value.GetType()).Merge(result.Restrictions),
_metaForwardee.Value
);
return restricted;
}
// Forward all dynamic operations or some of them as needed //
public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
{
return AddRestrictions(_metaForwardee.BindGetMember(binder));
}
public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
{
return AddRestrictions(_metaForwardee.BindSetMember(binder, value));
}
public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder)
{
return AddRestrictions(_metaForwardee.BindDeleteMember(binder));
}
public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes)
{
return AddRestrictions(_metaForwardee.BindGetIndex(binder, indexes));
}
public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value)
{
return AddRestrictions(_metaForwardee.BindSetIndex(binder, indexes, value));
}
public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes)
{
return AddRestrictions(_metaForwardee.BindDeleteIndex(binder, indexes));
}
public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
{
return AddRestrictions(_metaForwardee.BindInvokeMember(binder, args));
}
public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args)
{
return AddRestrictions(_metaForwardee.BindInvoke(binder, args));
}
public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args)
{
return AddRestrictions(_metaForwardee.BindCreateInstance(binder, args));
}
public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder)
{
return AddRestrictions(_metaForwardee.BindUnaryOperation(binder));
}
public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg)
{
return AddRestrictions(_metaForwardee.BindBinaryOperation(binder, arg));
}
public override DynamicMetaObject BindConvert(ConvertBinder binder)
{
return AddRestrictions(_metaForwardee.BindConvert(binder));
}
}
and so I wrote
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using ReactiveUI;
namespace Weingartner.Lens
{
public class Dyno : DynamicObject
{
private readonly DynamicNotifyingObject _D;
public Dyno(DynamicNotifyingObject d)
{
_D = d;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
bool ret = base.TryGetMember(binder, out result);
if (ret == false)
{
result = _D.GetPropertyValue(binder.Name);
if (result != null)
{
ret = true;
}
}
return ret;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
bool ret = base.TrySetMember(binder, value);
if (ret == false)
{
_D.SetPropertyValue(binder.Name, value);
ret = true;
}
return ret;
}
}
And the main object that inherits from ReactiveObject but we can also add dynamic properties to.
/// <summary>
/// An object you can add properties to at runtime which raises INPC events when those
/// properties are changed.
/// </summary>
[DataContract]
public class DynamicNotifyingObject : ReactiveObject, IDynamicMetaObjectProvider
{
#region Private Members
[DataMember]
private Dictionary<string, object> _dynamicProperties;
[DataMember]
private Dictionary<string, Type> _dynamicPropertyTypes;
[IgnoreDataMember]
private Dyno _dynamicObject { get; set; }
public Dyno DynamicObject
{
get
{
lock (this)
{
return _dynamicObject ?? (_dynamicObject = new Dyno(this));
}
}
}
#endregion Private Members
#region Constructor
public DynamicNotifyingObject() : this(new Tuple<string,Type>[] { }) { }
public DynamicNotifyingObject(IEnumerable<Tuple<string,Type>> propertyNames)
{
if (propertyNames == null)
{
throw new Exception("propertyNames is empty");
}
_dynamicProperties = new Dictionary<string, object>();
_dynamicPropertyTypes = new Dictionary<string, Type>();
foreach ( var prop in propertyNames )
{
AddProperty(prop.Item1, prop.Item2);
}
}
#endregion Constructor
#region Public Methods
public void AddProperty<T>( string propertyName, T initialValue )
{
_dynamicProperties.Add(propertyName, initialValue);
_dynamicPropertyTypes.Add(propertyName, typeof(T));
this.RaisePropertyChanged(propertyName);
}
public void AddProperty<T>( string propertyName)
{
AddProperty(propertyName, typeof(T));
}
public void AddProperty( string propertyName, Type type)
{
_dynamicProperties.Add(propertyName, null);
_dynamicPropertyTypes.Add(propertyName, type);
this.RaisePropertyChanged(propertyName);
}
public void SetPropertyValue<T>(string propertyName, T raw)
{
if (!_dynamicProperties.ContainsKey(propertyName))
{
throw new ArgumentException(propertyName + " property does not exist on " + GetType().Name);
}
var converter = DynamicLens2INPC.CreateConverter<T>(raw.GetType(), _dynamicPropertyTypes[propertyName]);
var value = converter(raw);
if (!value.Equals(_dynamicProperties[propertyName]))
{
_dynamicProperties[propertyName] = (object) value;
this.RaisePropertyChanged(propertyName);
}
}
public object GetPropertyValue(string propertyName)
{
if (!_dynamicProperties.ContainsKey(propertyName))
{
throw new ArgumentException(propertyName + " property does not exist " + GetType().Name);
}
return _dynamicProperties.ContainsKey(propertyName) ? _dynamicProperties[propertyName] : null;
}
#endregion Public Methods
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter)
{
return new ForwardingMetaObject(parameter, BindingRestrictions.Empty, this, DynamicObject,
// B's meta-object needs to know where to find the instance of B it is operating on.
// Assuming that an instance of A is passed to the 'parameter' expression
// we get the corresponding instance of B by reading the "B" property.
exprA => Expression.Property(exprA, nameof(DynamicObject))
);
}
}
public static class DynamicNotifyingObjectMixin
{
public static TRet RaiseAndSetIfChanged<TObj, TRet>(this TObj This, TRet newValue, ref TRet backingField, [CallerMemberName] string property = "")
where TObj : DynamicNotifyingObject
{
if (EqualityComparer<TRet>.Default.Equals(newValue, backingField))
{
return newValue;
}
This.RaisePropertyChanging(property);
backingField = newValue;
This.RaisePropertyChanged(property);
return newValue;
}
}
}
with a test case
using FluentAssertions;
using Xunit;
namespace Weingartner.Lens.Spec
{
public class DynamicNotifyingObjectSpec
{
class Fixture : DynamicNotifyingObject
{
public Fixture ():
base()
{
this.AddProperty<string>("A");
this.AddProperty<string>("B");
this.SetPropertyValue("A", "AAA");
this.SetPropertyValue("B", "BBB");
}
}
[Fact]
public void ShouldBeAbleToAddPropertiesLaterOn()
{
var ff = new Fixture();
ff.AddProperty<string>("newProp");
ff.AddProperty<string>("XXXX");
dynamic f = ff;
ff.SetPropertyValue("newProp", "CCC");
((object)(f.newProp)).Should().Be("CCC");
f.XXXX = "XXXX";
f.newProp = "DDD";
((object)(f.newProp)).Should().Be("DDD");
((object)(f.XXXX)).Should().Be("XXXX");
}
[Fact]
public void ShouldGenerateNotificationOnPropertyChange()
{
var a = new string []{"A"};
var b = new string []{"B"};
object oa = null;
object ob = null;
var f = new Fixture();
dynamic fd = f;
f.PropertyChanged += (sender, ev) =>
{
dynamic s = sender;
oa = s.A;
ob = s.B;
};
oa.Should().Be(null);
ob.Should().Be(null);
fd.A = "A";
oa.Should().Be("A");
ob.Should().Be("BBB");
fd.B = "B";
oa.Should().Be("A");
ob.Should().Be("B");
}
}
}
Another possibility is just to use this library
https://github.com/remi/MetaObject
using System;
using System.Dynamic;
public class MyClass : Whatever, IDynamicMetaObjectProvider {
// This 1 line is *ALL* you need to add support for all of the DynamicObject methods
public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression e)
{ return new MetaObject(e, this); }
// Now, if you want to handle dynamic method calls,
// you can implement TryInvokeMember, just like you would in DynamicObject!
public bool TryInvokeMember
(InvokeMemberBinder binder, object[] args, out object result) {
if (binder.Name.Contains("Cool")) {
result = "You called a method with Cool in the name!";
return true;
} else {
result = null;
return false;
}
}
}
and for my particular use case which is inheriting from ReactiveUI.Reactive object and having dynamic properties which support INPC I generated
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using ReactiveUI;
namespace Weingartner.Lens
{
/// <summary>
/// An object you can add properties to at runtime which raises INPC events when those
/// properties are changed.
/// </summary>
[DataContract]
public class DynamicNotifyingObject : ReactiveObject, IDynamicMetaObjectProvider
{
#region Private Members
[DataMember]
private Dictionary<string, object> _DynamicProperties;
[DataMember]
private Dictionary<string, Type> _DynamicPropertyTypes;
#endregion Private Members
#region Constructor
public DynamicNotifyingObject() : this(new Tuple<string,Type>[] { }) { }
public DynamicNotifyingObject(IEnumerable<Tuple<string,Type>> propertyNames)
{
if (propertyNames == null)
{
throw new Exception("propertyNames is empty");
}
_DynamicProperties = new Dictionary<string, object>();
_DynamicPropertyTypes = new Dictionary<string, Type>();
foreach ( var prop in propertyNames )
{
AddProperty(prop.Item1, prop.Item2);
}
}
#endregion Constructor
public void AddProperty<T>( string propertyName, T initialValue )
{
_DynamicProperties.Add(propertyName, initialValue);
_DynamicPropertyTypes.Add(propertyName, typeof(T));
this.RaisePropertyChanged(propertyName);
}
/// <summary>
/// Set the property. Will throw an exception if the property does not exist.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyName"></param>
/// <param name="raw"></param>
public void SetPropertyValue<T>(string propertyName, T raw)
{
if (!_DynamicProperties.ContainsKey(propertyName))
{
throw new ArgumentException(propertyName + " property does not exist on " + GetType().Name);
}
var converter = DynamicLens2INPC.CreateConverter<T>(raw.GetType(), _DynamicPropertyTypes[propertyName]);
var value = converter(raw);
if (!value.Equals(_DynamicProperties[propertyName]))
{
this.RaisePropertyChanging(propertyName);
_DynamicProperties[propertyName] = (object) value;
this.RaisePropertyChanged(propertyName);
}
}
/// <summary>
/// Get the property. Will throw an exception if the property does not exist.
/// </summary>
/// <param name="propertyName"></param>
/// <returns></returns>
public object GetPropertyValue(string propertyName)
{
if (!_DynamicProperties.ContainsKey(propertyName))
{
throw new ArgumentException(propertyName + " property does not exist " + GetType().Name);
}
return _DynamicProperties.ContainsKey(propertyName) ? _DynamicProperties[propertyName] : null;
}
public bool HasDynamicProperty(string propertyName) => _DynamicProperties.ContainsKey(propertyName);
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression e) { return new MetaObject(e, this); }
/// <summary>
/// Support for MetaObject. See https://github.com/remi/MetaObject
/// </summary>
/// <param name="binder"></param>
/// <param name="result"></param>
/// <returns></returns>
public bool TryGetMember(GetMemberBinder binder, out object result)
{
if (HasDynamicProperty(binder.Name))
{
result = GetPropertyValue(binder.Name);
return true;
}
// This path will return any real properties on the object
result = null;
return false;
}
/// <summary>
/// Support for MetaObject. See https://github.com/remi/MetaObject
/// </summary>
/// <param name="binder"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool TrySetMember(SetMemberBinder binder, object value)
{
if (HasDynamicProperty(binder.Name))
{
SetPropertyValue(binder.Name, value);
return true;
}
// This path will try to set any real properties on the object
return false;
}
}
}
Related
I am versed in the basics of Expression Trees, but I am having difficulty with this implementation:
I need to modify an instance of a class/interface, by finding every string property and encrypting it. I also need to recursively do the same to every "child" class/interface property, all the way down the rabbit hole. This includes IEnumerable<T> where T : class as well. I have the encryption sorted, but creating the expression try to do this for any T passed in is frankly beyond my understanding at this point.
I tried implementing this with reflection but performance quickly became an issue - here's what I've got so far:
Using the AggregateHelper and PropertyHelper classes from this post I was able to perform the basic string encryption:
Simple Test Classes:
public class SimpleEncryptionTest
{
public string String1 { get; set; }
}
public class NestedClassEncryptionTest
{
public string SomeString { get; set; }
public SimpleEncryptionTest Inner { get; set; }
}
public class ListClassEncryptionTest
{
public List<string> StringList { get; set; }
}
The unit tests I'm using to verify results:
[TestMethod]
public void Encryption_works_on_a_simple_class()
{
var testString = "this is only a test. If this had been a real emergency...";
var sut = new SimpleEncryptionTest() { String1 = testString };
sut.EncryptStringProperties();
Assert.AreNotEqual(testString, sut.String1);
}
[TestMethod]
public void Round_trip_works_on_a_simple_class()
{
var testString = "what string should I use?";
var sut = new SimpleEncryptionTest() { String1 = testString };
sut.EncryptStringProperties();
sut.DecryptStringProperties();
Assert.AreEqual(testString, sut.String1);
}
[TestMethod]
public void Round_trip_works_in_nested_class_scenario()
{
var outerString = "what is your name?";
var innerString = "Tony; what's your name?";
var sut = new NestedClassEncryptionTest{
SomeString = outerString,
Inner = new SimpleEncryptionTest(){String1 = innerString }
};
sut.EncryptStringProperties();
Assert.AreNotEqual(outerString, sut.SomeString);
Assert.AreNotEqual(innerString, sut.Inner.String1);
sut.DecryptStringProperties();
Assert.AreEqual(outerString, sut.SomeString);
Assert.AreEqual(innerString, sut.Inner.String1);
}
[TestMethod]
public void Round_trip_works_on_lists()
{
var testone = "one";
var testtwo = "two";
var testStrings = new List<string>() { testone, testtwo };
var sut = new ListClassEncryptionTest() { StringList = testStrings };
sut.EncryptStringProperties();
Assert.AreNotEqual(testone, sut.StringList[0]);
Assert.AreNotEqual(testtwo, sut.StringList[1]);
sut.DecryptStringProperties();
Assert.AreEqual(testone, sut.StringList[0]);
Assert.AreEqual(testtwo, sut.StringList[1]);
}
And the extension methods I'm using to Encrypt/Decrypt the values:
/// <summary>
/// Iterates through an Object's properties and encrypts any strings it finds,
/// recursively iterating any class or interface Properties.
/// </summary>
/// <typeparam name="T">Any type</typeparam>
/// <param name="genericObj">The object to encrypt</param>
/// <returns>The original object, with its string values encrypted.</returns>
public static T EncryptStringProperties<T>(this T obj)
where T : class
{
return Crypt(obj, EncryptStringAction);
}
/// <summary>
/// Iterates through an Object's properties and decrypts any strings it finds,
/// recursively iterating any class or interface Properties.
/// </summary>
/// <typeparam name="T">Any type</typeparam>
/// <param name="genericObj">The object to decrypt</param>
/// <returns>The original object, with its string values decrypted.</returns>
public static T DecryptStringProperties<T>(this T obj)
{
return Crypt(obj, DecryptStringAction);
}
private static T Crypt<T>(T obj, Action<PropertyHelper<T, string>, T> action)
{
var stringProperties = new AggregateHelper<T, string>();
foreach (var property in stringProperties.Properties)
{
var propertyHelper = stringProperties[property];
action(propertyHelper, obj);
}
// how do I find the nested classes, interfaces and lists
// using Expression Trees to feed them through the same processing?
return obj;
}
private static void EncryptStringAction<T>(PropertyHelper<T, string> prop, T genericObj)
{
var plainTextValue = (string)prop.GetValue(genericObj);
prop.SetValue(genericObj, plainTextValue.ToEncryptedString());
}
private static void DecryptStringAction<T>(PropertyHelper<T, string> prop, T genericObj)
{
var encryptedValue = (string)prop.GetValue(genericObj);
prop.SetValue(genericObj, encryptedValue.ToDecryptedString());
}
This works great as far as it goes; it encrypts string properties on any object, but I need to take it further - I need some way to recursively go "down the rabbit hole" - to create an AggregateHelper that selects properties that are instance objects (classes or interfaces) and feed those through the .EncryptStringProperties() extension method, as well as handling any IEnumerable with string values.
Try the following extension. It generates replacement Expression Tree for each object and compiles delegates for fast string replacement. All delegates are cached and replacement should be fast. It also handles recursive references and should omit Stack Overflow.
StringPropReplacer.ReplaceStrings(obj, s => Encrypt(s));
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
namespace StringReplacer
{
public static class StringPropReplacer
{
private static ParameterExpression _strParam = Expression.Parameter(typeof(string), "str");
private static ParameterExpression _originalValue = Expression.Variable(typeof(string), "original");
private static ParameterExpression _newValue = Expression.Variable(typeof(string), "newValue");
private static ParameterExpression _visitedParam = Expression.Parameter(typeof(HashSet<object>), "visited");
private static ParameterExpression _replacerParam = Expression.Parameter(typeof(Func<string, string>), "replacer");
private static ParameterExpression[] _blockVariables = new [] {_originalValue, _newValue};
private static void ReplaceObject<T>(T obj, HashSet<object> visited, Func<string, string> replacer)
{
ReflectionHolder<T>.ReplaceObject(obj, visited, replacer);
}
private static void ReplaceObjects<T>(IEnumerable<T> objects, HashSet<object> visited, Func<string, string> replacer)
{
ReflectionHolder<T>.ReplaceObjects(objects, visited, replacer);
}
public class ObjectReferenceEqualityComparer<T> : IEqualityComparer<T>
where T : notnull
{
public static IEqualityComparer<T> Default = new ObjectReferenceEqualityComparer<T>();
#region IEqualityComparer<T> Members
public bool Equals(T x, T y)
{
return ReferenceEquals(x, y);
}
public int GetHashCode(T obj)
{
if (obj == null)
return 0;
return RuntimeHelpers.GetHashCode(obj);
}
#endregion
}
private static class ReflectionHolder<T>
{
private static Action<T, HashSet<object>, Func<string, string>> _replacer;
static ReflectionHolder()
{
var objParam = Expression.Parameter(typeof(T), "obj");
var blockBody = new List<Expression>();
foreach (var prop in typeof(T).GetProperties())
{
if (prop.PropertyType == typeof(string) && prop.CanRead && prop.CanWrite)
{
var propExpression = Expression.MakeMemberAccess(objParam, prop);
blockBody.Add(Expression.Assign(_originalValue, propExpression));
blockBody.Add(Expression.Assign(_newValue, Expression.Invoke(_replacerParam, _originalValue)));
blockBody.Add(Expression.IfThen(Expression.NotEqual(_originalValue, _newValue),
Expression.Assign(propExpression, _newValue)));
}
else if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType))
{
var intf = prop.PropertyType
.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if (intf != null)
{
var propExpression = Expression.MakeMemberAccess(objParam, prop);
blockBody.Add(Expression.Call(typeof(StringPropReplacer), "ReplaceObjects",
intf.GetGenericArguments(), propExpression, _visitedParam, _replacerParam
));
}
}
else if (prop.PropertyType.IsClass)
{
var propExpression = Expression.MakeMemberAccess(objParam, prop);
blockBody.Add(Expression.Call(typeof(StringPropReplacer), "ReplaceObject",
new[] {prop.PropertyType}, propExpression, _visitedParam, _replacerParam
));
}
}
if (blockBody.Count == 0)
_replacer = (o, v, f) => { };
else
{
var replacerExpr = Expression.Lambda<Action<T, HashSet<object>, Func<string, string>>>(
Expression.Block(_blockVariables, blockBody), objParam, _visitedParam, _replacerParam);
_replacer = replacerExpr.Compile();
}
}
public static void ReplaceObject(T obj, HashSet<object> visited, Func<string, string> replacer)
{
if (obj == null)
return;
if (!visited.Add(obj))
return;
_replacer(obj, visited, replacer);
}
public static void ReplaceObjects(IEnumerable<T> objects, HashSet<object> visited, Func<string, string> replacer)
{
if (objects == null)
return;
if (!visited.Add(objects))
return;
foreach (var obj in objects)
{
ReplaceObject(obj, visited, replacer);
}
}
}
public static void ReplaceStrings<T>(T obj, Func<string, string> replacer)
{
ReplaceObject(obj, new HashSet<object>(ObjectReferenceEqualityComparer<object>.Default), replacer);
}
}
}
I need a container that works like a ditionary but where the type of data (TValue) change from one key to the other.
I also need to iterate trough it.
For the heterogeneous and type-safe dictionary part
Wilka response is a good start.
The trick is to put the type in the key.
/// <summary>
/// Base class for all dictionary key.
///
/// <remarks>The key name is REALLY usefull for debug purpose.</remarks>
/// </summary>
abstract class HeterogeneousDictionaryKeyBase
{
readonly string _name;
protected HeterogeneousDictionaryKeyBase(string name)
{
_name = name;
}
public override string ToString()
{
return _name;
}
}
sealed class HeterogeneousDictionaryKey<TValue> : HeterogeneousDictionaryKeyBase
{
public HeterogeneousDictionaryKey(string name)
: base(name)
{
}
}
So calls to dictionary will have a generic value type:
/// <summary>
/// <remarks>The [] operator can not be generic, so we implement it has a getter and a setter</remarks>
/// </summary>
class HeterogeneousDictionary
{
private readonly Dictionary<HeterogeneousDictionaryKeyBase, object> _dictionary = new Dictionary<HeterogeneousDictionaryKeyBase, object>();
public void Add<TValue>(HeterogeneousDictionaryKey<TValue> key, TValue value)
{
_dictionary.Add(key, value);
}
public TValue Get<TValue>(HeterogeneousDictionaryKey<TValue> key)
{
return (TValue)_dictionary[key];
}
public void Set<TValue>(HeterogeneousDictionaryKey<TValue> key, TValue value)
{
_dictionary[key] = value;
}
public bool TryGetValue<TValue>(HeterogeneousDictionaryKey<TValue> key, out TValue value)
{
object result;
if (_dictionary.TryGetValue(key, out result) && result is TValue)
{
value = (TValue)result;
return true;
}
value = default(TValue);
return false;
}
}
The usage is simple:
var dictionary = new HeterogeneousDictionary();
var keyName = new HeterogeneousDictionaryKey<string>("keyName");
var keyAge = new HeterogeneousDictionaryKey<int>("keyAge");
dictionary.Set(keyName, "Orace");
dictionary.Set(keyAge, 8);
...
var name = dictionary.Get(keyName);
var age = dictionary.Get(keyAge);
For the iteration part
A visitor pattern against the dictionary keys will do the trick.
First the visitor interface:
interface IHeterogeneousDictionaryKeyVisitor
{
void Visit<TValue>(HeterogeneousDictionaryKey<TValue> key);
}
Then we made the HeterogeneousDictionaryKey cooperate:
abstract class HeterogeneousDictionaryKeyBase
{
...
public abstract void Accept(IHeterogeneousDictionaryKeyVisitor visitor);
...
}
sealed class HeterogeneousDictionaryKey<TValue> : HeterogeneousDictionaryKeyBase
{
...
public override void Accept(IHeterogeneousDictionaryKeyVisitor visitor)
{
visitor.Visit(this);
}
}
Now we can expose the HeterogeneousDictionary keys:
class HeterogeneousDictionary
{
...
public Dictionary<HeterogeneousDictionaryKeyBase, object>.KeyCollection Keys
{
get { return _dictionary.Keys; }
}
...
}
And that it all.
Here an example of usage to safely copy a dictionary to an other
class DictionaryCopier : IHeterogeneousDictionaryKeyVisitor
{
readonly HeterogeneousDictionary _source;
readonly HeterogeneousDictionary _destination;
public DictionaryCopier(HeterogeneousDictionary source, HeterogeneousDictionary destination)
{
_source = source;
_destination = destination;
}
public void PerformCopy()
{
foreach (var key in _source.Keys)
{
// See you soon.
key.Accept(this);
}
}
/// <summary>
/// We fall back here with a typed key.
/// </summary>
public void Visit<TValue>(HeterogeneousDictionaryKey<TValue> key)
{
// Here the value is typed.
var value = _source.Get(key);
_destination.Add(key, value);
}
}
I have a working PATCH for my user class with Delta in Web API 2. By using the .patch method I can easily detect only the changes that were sent over and then update accordingly, rather than have to receive the entire user!
The problem is there are several fields that I want to protect so they are never updated.
I saw one example on SO but it didn't leverage Delta rather seemed to be slightly more dated and practically wrote all of the patch code by hand. Is there not a way to easily tell OData's patch to skip over properties you designate (maybe I need to override patch and tell it to avoid some properties)?
How would I even begin to go about doing this (or what should I search for / research to get started)? Do action filters / validation have a role here? Do I look into model binding? Is it overriding patch?
Thanks!
Depending on what you want to do if someone tries to update protected fields you can either:
Update only fields that can be modified. For this you can construct new Delta with only these fields like this:
Delta<User> filteredDelta = new Delta<User>();
if (originalDelta.GetChangedPropertyNames().Contains("FirstName"))
{
filteredDelta.TrySetPropertyValue("FirstName", originalDelta.GetEntity().FirstName);
}
if (originalDelta.GetChangedPropertyNames().Contains("LastName"))
{
filteredDelta.TrySetPropertyValue("LastName", originalDelta.GetEntity().LastName);
}
filteredDelta.Patch(selectedUser);
Fail the PATCH request (I would say this is preferred and least surprising way to deal with such requests). This would be even simpler:
if (originalDelta.GetChangedPropertyNames().Contains("ModifiedDate"))
{
return InternalServerError(new ArgumentException("Attribue is read-only", "ModifiedDate"));
}
There's a couple of possibilities, depending on you use case...
You want to exclude the changes if they are supplied
You want to throw an error if non-editable fields are updated.
Start with an attribute to mark appropriate properties
/// <summary>
/// Marks a property as non-editable.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class NonEditableAttribute : Attribute
{
}
Then we can write some extensions against Delta to take advantage of this
public static class PatchExtensions
{
/// <summary>
/// Get the properties of a type that are non-editable.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IList<string> NonEditableProperties(this Type type)
{
return type.GetProperties().Where(x => Attribute.IsDefined(x, typeof(NonEditableAttribute))).Select(prop => prop.Name).ToList();
}
/// <summary>
/// Get this list of non-editable changes in a <see cref="Delta{T}"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="delta"></param>
/// <returns></returns>
public static IList<string> NonEditableChanges<T>(this Delta<T> delta)
where T : class
{
var nec = new List<string>();
var excluded = typeof(T).NonEditableProperties();
nec.AddRange(delta.GetChangedPropertyNames().Where(x => excluded.Contains(x)));
return nec;
}
/// <summary>
/// Exclude changes from a <see cref="Delta{T}"/> based on a list of property names
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="delta"></param>
/// <param name="excluded"></param>
/// <returns></returns>
public static Delta<T> Exclude<T>(this Delta<T> delta, IList<string> excluded)
where T : class
{
var changed = new Delta<T>();
foreach (var prop in delta.GetChangedPropertyNames().Where(x => !excluded.Contains(x)))
{
object value;
if (delta.TryGetPropertyValue(prop, out value))
{
changed.TrySetPropertyValue(prop, value);
}
}
return changed;
}
/// <summary>
/// Exclude changes from a <see cref="Delta{T}"/> where the properties are marked with <see cref="NonEditableAttribute"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="delta"></param>
/// <returns></returns>
public static Delta<T> ExcludeNonEditable<T>(this Delta<T> delta)
where T : class
{
var excluded = typeof(T).NonEditableProperties();
return delta.Exclude(excluded);
}
}
And a domain class
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
[NonEditable]
public string SecurityId { get; set; }
}
Finally your controller can then take advantage of this in the Patch method
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Customer> delta)
{
var patch = delta.ExcludeNonEditable();
// TODO: Your patching action here
}
or
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Customer> delta)
{
var nonEditable = delta.NonEditableChanges();
if (nonEditable.Count > 0)
{
throw new HttpException(409, "Cannot update as non-editable fields included");
}
// TODO: Your patching action here
}
I had the same need and I ended up writing an extension method to Delta that accepts additional parameters to limit which fields to update (similar to TryUpDateModel)
I know there must be a better way to do this, but for now this works.
I had to recreate some of the Delta private methods and classes. Most of the code is from https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Http.OData/OData/Delta.cs, https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/OData/src/System.Web.Http.OData/OData/PropertyAccessor.cs and https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Http.OData/OData/CompiledPropertyAccessor.cs (or similar, these are not the exact url's I copied from)
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Linq.Expressions;
namespace MyProject.ODataExtensions
{
public static class ODataExtensions
{
public static void Patch<TEntityType>(this System.Web.OData.Delta<TEntityType> d, TEntityType original, String[] includedProperties, String[] excludedProperties) where TEntityType : class
{
Dictionary<string, PropertyAccessor<TEntityType>> _propertiesThatExist = InitializePropertiesThatExist<TEntityType>();
var changedProperties = d.GetChangedPropertyNames();
if (includedProperties != null) changedProperties = changedProperties.Intersect(includedProperties);
if (excludedProperties != null) changedProperties = changedProperties.Except(excludedProperties);
PropertyAccessor<TEntityType>[] array = (
from s in changedProperties
select _propertiesThatExist[s]).ToArray();
var array2 = array;
for (int i = 0; i < array2.Length; i++)
{
PropertyAccessor<TEntityType> propertyAccessor = array2[i];
propertyAccessor.Copy(d.GetEntity(), original);
}
}
private static Dictionary<string, PropertyAccessor<T>> InitializePropertiesThatExist<T>() where T : class
{
Type backingType = typeof(T);
return backingType.GetProperties()
.Where(p => p.GetSetMethod() != null && p.GetGetMethod() != null)
.Select<PropertyInfo, PropertyAccessor<T>>(p => new CompiledPropertyAccessor<T>(p))
.ToDictionary(p => p.Property.Name);
}
internal abstract class PropertyAccessor<TEntityType> where TEntityType : class
{
protected PropertyAccessor(PropertyInfo property)
{
if (property == null)
{
throw new System.ArgumentException("Property cannot be null","property");
}
Property = property;
if (Property.GetGetMethod() == null || Property.GetSetMethod() == null)
{
throw new System.ArgumentException("Property must have public setter and getter", "property");
}
}
public PropertyInfo Property
{
get;
private set;
}
public void Copy(TEntityType from, TEntityType to)
{
if (from == null)
{
throw new System.ArgumentException("Argument cannot be null", "from");
}
if (to == null)
{
throw new System.ArgumentException("Argument cannot be null", "to");
}
SetValue(to, GetValue(from));
}
public abstract object GetValue(TEntityType entity);
public abstract void SetValue(TEntityType entity, object value);
}
internal class CompiledPropertyAccessor<TEntityType> : PropertyAccessor<TEntityType> where TEntityType : class
{
private Action<TEntityType, object> _setter;
private Func<TEntityType, object> _getter;
public CompiledPropertyAccessor(PropertyInfo property)
: base(property)
{
_setter = MakeSetter(Property);
_getter = MakeGetter(Property);
}
public override object GetValue(TEntityType entity)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
return _getter(entity);
}
public override void SetValue(TEntityType entity, object value)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
_setter(entity, value);
}
private static Action<TEntityType, object> MakeSetter(PropertyInfo property)
{
Type type = typeof(TEntityType);
ParameterExpression entityParameter = Expression.Parameter(type);
ParameterExpression objectParameter = Expression.Parameter(typeof(Object));
MemberExpression toProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property);
UnaryExpression fromValue = Expression.Convert(objectParameter, property.PropertyType);
BinaryExpression assignment = Expression.Assign(toProperty, fromValue);
Expression<Action<TEntityType, object>> lambda = Expression.Lambda<Action<TEntityType, object>>(assignment, entityParameter, objectParameter);
return lambda.Compile();
}
private static Func<TEntityType, object> MakeGetter(PropertyInfo property)
{
Type type = typeof(TEntityType);
ParameterExpression entityParameter = Expression.Parameter(type);
MemberExpression fromProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property);
UnaryExpression convert = Expression.Convert(fromProperty, typeof(Object));
Expression<Func<TEntityType, object>> lambda = Expression.Lambda<Func<TEntityType, object>>(convert, entityParameter);
return lambda.Compile();
}
}
}
}
I have a class MyClassA that has an IList property. I am using a PropertyGrid control to display all the properties of MyClassA and I would like the list of MyClassB to be displayed and editable via the PropertyGrid for MyClassA.
I currently have all the other properties being displayed in the Property grid except for the property that is the list of MyClassB. How do I go about adding the List of MyClassB to the property grid where the user can add/edit/remove items from the List?
I haven't really been able to find any examples that go into detail on this as of yet although I am still digging.
Here is a solution I have worked out so far, although it still doesn't fit in 100% to what I am looking for.
I found this reference to modify for my liking: http://www.codeproject.com/KB/tabs/customizingcollectiondata.aspx
What I did was create a new class that inherits from CollectionBase and that uses an ICustomTypeDescriptor.
After I did this and implemented the basic functionality, I had to create a PropertyDescriptor for the class.
Here is the code:
public class ZoneCollection : CollectionBase, ICustomTypeDescriptor
{
#region Collection Implementation
/// <summary>
/// Adds an zone object to the collection
/// </summary>
/// <param name="emp"></param>
public void Add(Zone zone)
{
this.List.Add(zone);
}
/// <summary>
/// Removes an zone object from the collection
/// </summary>
/// <param name="emp"></param>
public void Remove(Zone zone)
{
this.List.Remove(zone);
}
/// <summary>
/// Returns an zone object at index position.
/// </summary>
public Zone this[int index]
{
get
{
return (Zone)this.List[index];
}
}
#endregion
// Implementation of interface ICustomTypeDescriptor
#region ICustomTypeDescriptor impl
public String GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public String GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
/// <summary>
/// Called to get the properties of this type. Returns properties with certain
/// attributes. this restriction is not implemented here.
/// </summary>
/// <param name="attributes"></param>
/// <returns></returns>
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetProperties();
}
/// <summary>
/// Called to get the properties of this type.
/// </summary>
/// <returns></returns>
public PropertyDescriptorCollection GetProperties()
{
// Create a collection object to hold property descriptors
PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);
// Iterate the list of employees
for (int i = 0; i < this.List.Count; i++)
{
// Create a property descriptor for the zone item and add to the property descriptor collection
ZoneCollectionPropertyDescriptor pd = new ZoneCollectionPropertyDescriptor(this, i);
pds.Add(pd);
}
// return the property descriptor collection
return pds;
}
#endregion
}
/// <summary>
/// Summary description for CollectionPropertyDescriptor.
/// </summary>
public class ZoneCollectionPropertyDescriptor : PropertyDescriptor
{
private ZoneCollection collection = null;
private int index = -1;
public ZoneCollectionPropertyDescriptor(ZoneCollection coll, int idx) :
base("#" + idx.ToString(), null)
{
this.collection = coll;
this.index = idx;
}
public override AttributeCollection Attributes
{
get
{
return new AttributeCollection(null);
}
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get
{
return this.collection.GetType();
}
}
public override string DisplayName
{
get
{
Zone zone = this.collection[index];
return zone.ID.ToString();
}
}
public override string Description
{
get
{
Zone zone = this.collection[index];
StringBuilder sb = new StringBuilder();
sb.Append(zone.ID.ToString());
if ( zone.Streets.Route != String.Empty || zone.Streets.Crossing != String.Empty)
sb.Append("::");
if (zone.Streets.Route != String.Empty)
sb.Append(zone.Streets.Route);
if ( zone.Streets.Crossing != String.Empty)
{
sb.Append(" and ");
sb.Append(zone.Streets.Crossing);
}
return sb.ToString();
}
}
public override object GetValue(object component)
{
return this.collection[index];
}
public override bool IsReadOnly
{
get { return false; }
}
public override string Name
{
get { return "#" + index.ToString(); }
}
public override Type PropertyType
{
get { return this.collection[index].GetType(); }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override void SetValue(object component, object value)
{
// this.collection[index] = value;
}
}
Intersection now contains a ZoneCollection instead of an IList and I can now edit/add/remove the zones contained within the collection.
Now, if I could make this more generic I'd be relatively happy. Another hindrance for my model is that I had to inherit from Collection base using this, instead of IList. This completely broke my mapping of my class for NHibernate and I'm now having to try and figure out how to remap this list using the method mentioned above.
If anyone wants to elaborate this any further I'd greatly appreciate some more insight.
I know this Topic is more than 2 years old, but maybe this could be interesting for you.
I had a similar Problem.
Starting with: I need a Point in 3D-Space which should be configurable in Property-Grid
For this I created a Class Koord. To make it changeable in PropertyGrid, I created a new Class "KoordConverter : TypeConverter"
This is used in a Vexel (check Wikipedia to find out what it's for :-) )
To create an TestBock (some 3D-Object) I'm using a List of Vexels.
Unfortunately I need a List of TestBlocks in my Program, Visible through the Property-Grid.
To start Topmost:
public partial class FormMain : Form
{
private BlockProperties _bp = new BlockProperties();
public FormMain()
{
InitializeComponent();
pgProperties.SelectedObject = _bp;
}
[...]
}
The Class BlockProperties includes the List of TestBocks which I filled a bit to show you what's inside.
class BlockProperties
{
public List<TestBocks> Testing { get; set; }
public BlockProperties()
{
Testing = new List<TestBocks>(3);
List<Vexel> t1 = new List<Vexel>(1);
t1.Add(new Vexel(new Koord(1,0,1), 1));
List<Vexel> t2 = new List<Vexel>(2);
t2.Add(new Vexel(new Koord(2, 0, 1), 2));
t2.Add(new Vexel(new Koord(2, 0, 2), 2));
List<Vexel> t3 = new List<Vexel>(3);
t3.Add(new Vexel(new Koord(3, 0, 1), 3));
t3.Add(new Vexel(new Koord(3, 0, 2), 3));
t3.Add(new Vexel(new Koord(3, 0, 3), 3));
TestBocks tb1 = new TestBocks();
tb1.Koords = t1;
TestBocks tb2 = new TestBocks();
tb2.Koords = t2;
TestBocks tb3 = new TestBocks();
tb3.Koords = t3;
Testing.Add(tb1);
Testing.Add(tb2);
Testing.Add(tb3);
[...]
}
[...]
}
Next is my TestBlock class, which is simply straight forward
[Serializable]
public class TestBocks
{
public List<Vexel> Vexels{ get; set; }
public TestBocks()
{
Vexels = new List<Vexel>();
}
}
In the Vexels is most of the magic I need for my Program:
I even put a ToString() here to make it easy during debugging.
public class Vexel
{
private Koord _origin;
private double _extent;
public Koord Origin { get { return _origin; } set { _origin = value; } }
public double Extent { get { return _extent; } set { _extent = value; } }
public string ToString()
{
NumberFormatInfo nFormatInfo = new NumberFormatInfo
{
NumberDecimalSeparator = ".",
NumberGroupSeparator = ""
};
return String.Format(nFormatInfo, "Origin;{0};{1};{2};Extent;{3}", _origin.X, _origin.Y, _origin.Z, _extent);
}
public Vexel()
{
_origin = new Koord(0,0,0);
Extent = 0;
}
public Vexel(Koord origin, double extent)
{
//TODO do some checking
_origin = origin;
_extent = extent;
}
So far everything worked fine for the PropertyGrid, but I could not edit the Koords.
The Class was pretty simple but not editable in the PropertyGrid.
Adding a TypeConverterClass solved this Problem (you can find the TypeConverter below the code of the Koord)
[TypeConverter(typeof(KoordConverter))]
[Serializable]
public class Koord
{
private double p_1;
private double p_2;
private double p_3;
public Koord(double x, double y, double z)
{
this.p_1 = x;
this.p_2 = y;
this.p_3 = z;
}
public string ToString()
{
return String.Format("X;{0};Y;{1};Z;{2}", p_1, p_2, p_3);
}
public double X { get { return p_1; } }
public double Y { get { return p_2; } }
public double Z { get { return p_3; } }
}
The Typeconverter was the most complicated code to write. You can find it below:
public class KoordConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(InstanceDescriptor) || base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string text = value as string;
if (text == null)
{
return base.ConvertFrom(context, culture, value);
}
string text2 = text.Trim();
if (text2.Length == 0)
{
return null;
}
if (culture == null)
{
culture = CultureInfo.CurrentCulture;
}
char c = culture.TextInfo.ListSeparator[0];
string[] array = text2.Split(new char[]
{
c
});
int[] array2 = new int[array.Length];
TypeConverter converter = TypeDescriptor.GetConverter(typeof(int));
for (int i = 0; i < array2.Length; i++)
{
array2[i] = (int)converter.ConvertFromString(context, culture, array[i]);
}
if (array2.Length == 3)
{
return new Koord(array2[0], array2[1], array2[2]);
}
throw new ArgumentException("TextParseFailedFormat");
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == null)
{
throw new ArgumentNullException("destinationType");
}
if (value is Koord)
{
if (destinationType == typeof(string))
{
Koord Koord = (Koord)value;
if (culture == null)
{
culture = CultureInfo.CurrentCulture;
}
string separator = culture.TextInfo.ListSeparator + " ";
TypeConverter converter = TypeDescriptor.GetConverter(typeof(int));
string[] array = new string[3];
int num = 0;
array[num++] = converter.ConvertToString(context, culture, Koord.X);
array[num++] = converter.ConvertToString(context, culture, Koord.Y);
array[num++] = converter.ConvertToString(context, culture, Koord.Z);
return string.Join(separator, array);
}
if (destinationType == typeof(InstanceDescriptor))
{
Koord Koord2 = (Koord)value;
ConstructorInfo constructor = typeof(Koord).GetConstructor(new Type[]
{
typeof(double),
typeof(double),
typeof(double)
});
if (constructor != null)
{
return new InstanceDescriptor(constructor, new object[]
{
Koord2.X,
Koord2.Y,
Koord2.Z
});
}
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
{
if (propertyValues == null)
{
throw new ArgumentNullException("propertyValues");
}
object obj = propertyValues["X"];
object obj2 = propertyValues["Y"];
object obj3 = propertyValues["Z"];
if (obj == null || obj2 == null || obj3 == null || !(obj is double) || !(obj2 is double) || !(obj3 is double))
{
throw new ArgumentException("PropertyValueInvalidEntry");
}
return new Koord((double)obj, (double)obj2, (double)obj3);
}
public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(Koord), attributes);
return properties.Sort(new string[]
{
"X",
"Y",
"Z"
});
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
}
Basically after all of this was set up, It was no ploblem to modify any List of objects (TestBlocks or the Vexels within each TestBlock)
Hope it helps someone if they step over this Thread.
Best Regards
Robin Blood
PS:Editing is no problem in the PropertyGrid, maybe you just didn't get your constructors right !?
http://i.stack.imgur.com/LD3zf.png
So I've got a ConfigurationSection/ConfigurationElementCollection that has a configuration like this:
<mimeFormats>
<add mimeFormat="text/html" />
</mimeFormats>
And here is how I handle the mimeFormats:
public class MimeFormatElement: ConfigurationElement
{
#region Constructors
/// <summary>
/// Predefines the valid properties and prepares
/// the property collection.
/// </summary>
static MimeFormatElement()
{
// Predefine properties here
_mimeFormat = new ConfigurationProperty(
"mimeFormat",
typeof(MimeFormat),
"*/*",
ConfigurationPropertyOptions.IsRequired
);
}
private static ConfigurationProperty _mimeFormat;
private static ConfigurationPropertyCollection _properties;
[ConfigurationProperty("mimeFormat", IsRequired = true)]
public MimeFormat MimeFormat
{
get { return (MimeFormat)base[_mimeFormat]; }
}
}
public class MimeFormat
{
public string Format
{
get
{
return Type + "/" + SubType;
}
}
public string Type;
public string SubType;
public MimeFormat(string mimeFormatStr)
{
var parts = mimeFormatStr.Split('/');
if (parts.Length != 2)
{
throw new Exception("Invalid MimeFormat");
}
Type = parts[0];
SubType = parts[1];
}
}
And obviously I need a TypeConverter that actually does something (instead of this empty shell):
public class MimeFormatConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
throw new NotImplementedException();
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
throw new NotImplementedException();
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
throw new NotImplementedException();
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
throw new NotImplementedException();
}
}
How do I set up a TypeConverter that will allow type conversion from/to string? I've tried using the MSDN examples but I keep getting error message:
TypeConverter cannot convert from System.String.
Essentially, how can it be set up so that it will just work with whatever ConfigurationSection is trying to do?
You can put TypeConverterAttribute on the property to tell the serializer how to handle it.
[TypeConverter(typeof(MimeFormatConverter))]
[ConfigurationProperty("mimeFormat", IsRequired = true)]
public MimeFormat MimeFormat
{
get { return (MimeFormat)base[_mimeFormat]; }
}
Try this:
TestSection.cs
public class TestSection : ConfigurationSection
{
private static readonly ConfigurationProperty sFooProperty = new ConfigurationProperty("Foo",
typeof(Foo),
null,
new FooTypeConverter(),
null,
ConfigurationPropertyOptions.None);
public static readonly ConfigurationPropertyCollection sProperties = new ConfigurationPropertyCollection();
static TestSection()
{
sProperties.Add(sFooProperty);
}
public Foo Foo
{
get { return (Foo)this[sFooProperty]; }
set { this[sFooProperty] = value; }
}
protected override ConfigurationPropertyCollection Properties
{
get { return sProperties; }
}
}
Foo.cs
public class Foo
{
public string First { get; set; }
public string Second { get; set; }
public override string ToString()
{
return First + ',' + Second;
}
}
FooTypeConverter.cs
public class FooTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType == typeof(string));
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string val = value as string;
if (val != null)
{
string[] parts = val.Split(',');
if (parts.Length != 2)
{
// Throw an exception
}
return new Foo { First = parts[0], Second = parts[1] };
}
return null;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType == typeof(string));
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
Foo val = value as Foo;
if (val != null)
return val.ToString();
return null;
}
}
I figured it out. Here is the solution:
public class MimeFormatElement: ConfigurationElement
{
#region Constructors
/// <summary>
/// Predefines the valid properties and prepares
/// the property collection.
/// </summary>
static MimeFormatElement()
{
// Predefine properties here
_mimeFormat = new ConfigurationProperty(
"mimeFormat",
typeof(MimeFormat),
"*/*",
ConfigurationPropertyOptions.IsRequired
);
_properties = new ConfigurationPropertyCollection {
_mimeFormat, _enabled
};
}
private static ConfigurationProperty _mimeFormat;
private static ConfigurationPropertyCollection _properties;
[ConfigurationProperty("mimeFormat", IsRequired = true)]
public MimeFormat MimeFormat
{
get { return (MimeFormat)base[_mimeFormat]; }
}
}
/*******************************************/
[TypeConverter(typeof(MimeFormatConverter))]
/*******************************************/
public class MimeFormat
{
public string Format
{
get
{
return Type + "/" + SubType;
}
}
public string Type;
public string SubType;
public MimeFormat(string mimeFormatStr)
{
var parts = mimeFormatStr.Split('/');
if (parts.Length != 2)
{
throw new Exception("Invalid MimeFormat");
}
Type = parts[0];
SubType = parts[1];
}
}
public class MimeFormatConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return new MimeFormat((string)value);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
var val = (MimeFormat)value;
return val.Type + "/" + val.SubType;
}
}
From this point, you have to create the convert sections within the ConvertTo and ConvertFrom methods
public override object ConvertFrom( ITypeDescriptorContext context, CultureInfo culture, object value ) {
if ( value == null )
return null;
try {
if ( value is string ) {
string s = (string)value;
// here is where you look at the string to figure out the MimeFormat
// like so....
return new MimeFormat( s );
}
throw new NotSupportedException( NotSupportedException( value.GetType(), typeof(MimeFormat) );
}
public override object ConvertTo( ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType ) {
if ( value == null )
return null;
MimeFormat p = (MimeFormat)value;
if ( destinationType == typeof( String ) )
return p.ToString();
throw new NotSupportedException( NotSupportedException( typeof(MimeFormat), destinationType ) );
}
EDITED
You also need to override the CanConvert functions as well.
public override bool CanConvertFrom( ITypeDescriptorContext context, Type sourceType ) {
if ( sourceType == typeof( string ) )
return true;
return false;
}
public override bool CanConvertTo( ITypeDescriptorContext context, Type destinationType ) {
if ( destinationType == typeof( string ) )
return true;
return false;
}