I am writing a sort of MsCharts designer.
- Design Chart, ChartAreas, Series,...
- The object is saved via the standard System.Windows.Forms.DataVisualization.Charting.Chart.ChartSerializer
I want the user to be able to add multiple Striplines to the Axis's.
I am attempting to identify a StripLine within the StripLines Collection of an Axis.
The Name property of a StripLine is read only (get, no set).
I see no way to actually set the Name property.
I do not understand how this is useful?
I was going to use the Tag property of the StripLine but alas the Tag property is not serialized.
Note:
If I edit the serialized chart and add Tag="AStripLine" to a element and then load it via Chart.ChartSerializer The Tag= value is in fact there.
If I save / serialize the chart via Chart.ChartSerializer Tag is not saved.
Any help / ideas would be greatly appreciated.
Tag property is of type of object and it's decorated with an internal attribute telling the serializer to not serialize Tag property. So the behavior is expected.
But since the serializer relies on TypeDescriptor, you can create a new TypeDescriptor for StripLine class describe Tag property in a different way, for example:
Make it browsable in property grid
Make it editable in property grid
Make it serializable for chart serializer
So it serializes and deserializes it correctly with this format, for example:
<StripLine Text="text1" Tag="1" />
Also shows it in property grid at run-time:
You need to create the following classes:
StripLineTypeDescriptionProvider: Helps to register a new type descriptor for StripLine
StripLineTypeDescriptor: Describes the properties of the type and allows you to change Tag property behavior. In this class we override GetProperties and replace the Tag property with a modified property descriptor which tells the serializer to serialize Tag and also tells the property grid to show it and make it editable.
MyPropertyDescriptor: Helps us to specify the new type of Tag property. You can decide to set it as string, int or even a complex type. It's enough the type be convertible to and from string.
Then it's enough to register the type descriptor for StripLine in constructor or load event of the form:
var provider = new StripLineTypeDescriptionProvider();
TypeDescriptor.AddProvider(provider, typeof(StripLine));
Implementations
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
public class StripLineTypeDescriptionProvider : TypeDescriptionProvider
{
public StripLineTypeDescriptionProvider()
: base(TypeDescriptor.GetProvider(typeof(object))) { }
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
ICustomTypeDescriptor baseDescriptor = base.GetTypeDescriptor(objectType, instance);
return new StripLineTypeDescriptor(baseDescriptor);
}
}
public class StripLineTypeDescriptor : CustomTypeDescriptor
{
ICustomTypeDescriptor original;
public StripLineTypeDescriptor(ICustomTypeDescriptor originalDescriptor)
: base(originalDescriptor)
{
original = originalDescriptor;
}
public override PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(new Attribute[] { });
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = base.GetProperties(attributes).Cast<PropertyDescriptor>().ToList();
var tag = properties.Where(x => x.Name == "Tag").FirstOrDefault();
var tagAttributes = tag.Attributes.Cast<Attribute>()
.Where(x => x.GetType() != typeof(BrowsableAttribute)).ToList();
var serializationAttribute = tagAttributes.Single(
x => x.GetType().FullName == "System.Windows.Forms.DataVisualization.Charting.Utilities.SerializationVisibilityAttribute");
var visibility = serializationAttribute.GetType().GetField("_visibility",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
visibility.SetValue(serializationAttribute, Enum.Parse(visibility.FieldType, "Attribute"));
tagAttributes.Add(new BrowsableAttribute(true));
var newTag = new MyPropertyDescriptor(tag, tagAttributes.ToArray());
properties.Remove(tag);
properties.Add(newTag);
return new PropertyDescriptorCollection(properties.ToArray());
}
}
public class MyPropertyDescriptor : PropertyDescriptor
{
PropertyDescriptor o;
public MyPropertyDescriptor(PropertyDescriptor originalProperty,
Attribute[] attributes) : base(originalProperty)
{
o = originalProperty;
AttributeArray = attributes;
}
public override bool CanResetValue(object component)
{ return o.CanResetValue(component); }
public override object GetValue(object component) => o.GetValue(component);
public override void ResetValue(object component) { o.ResetValue(component); }
public override void SetValue(object component, object value) { o.SetValue(component, value); }
public override bool ShouldSerializeValue(object component) => true;
public override AttributeCollection Attributes => new AttributeCollection(AttributeArray);
public override Type ComponentType => o.ComponentType;
public override bool IsReadOnly => false;
public override Type PropertyType => typeof(string);
}
References
Here are source code for classes which will help you to learn how the chart serialization works:
ChartElement
ChartSerializer
SerializerBase
XmlFormatSerializer
This is truely a weird finding!
At first I thought that maybe the Names would be autogenerated in a useful way, say StripLine1, StripLine2 etc.
But they all get StripLine as their Name.
So it will be useless for your purpose of identifiying them.
There is however the Tag property that comes to the rescue; it easy to e.g. set to a unique string..:
StripLine sl = new StripLine()
{ Text = "LW" , StripWidth = 2, ForeColor = Color.Teal, Tag = "Low-Water"};
To make it unique for an axis AxisY could use this:
StripLine sl = new StripLine()
{ Text = "LW" , StripWidth = 2, ForeColor = Color.Teal,
Tag = "Low-Water" + chart1.ChartAreas[0].AxisY.StripLines.Count };
As Tag is of type object you could create a class to hold more info, like a shortname and a description..
Update: I just noticed that you know about Tags and how they are not serialized. You could however use this workaround:
Before Serializing you loop over all StripLines and change the Text to : oldText + separator + Tag string.
After de-serializing you do the reverse.
As separator you could use the tab (\t) or other characters (or strings) you don't expect in the texts.. (The vertical tab, my original idea is not an allowed xml entity..)
Here is a function to prepare the Text and to reconstruct the Tags:
void StripLineTagger(Chart chart, bool beforeSer)
{
char sep = '\t';
var axes = new List<Axis> { chart.ChartAreas[0].AxisX, chart.ChartAreas[0].AxisX2,
chart.ChartAreas[0].AxisY, chart.ChartAreas[0].AxisY2};
foreach (var ax in axes)
foreach (var sl in ax.StripLines)
{
if (beforeSer) sl.Text = sl.Text + sep + sl.Tag.ToString();
else
{
var p = sl.Text.Split(sep);
sl.Text = p[0];
sl.Tag = p[1];
}
}
}
This is untested and lacks all checks..!
Update 2:
You could add a subclass of your own to replace the regular StripLines:
class MyStripLine : StripLine
{
new public string Name { get; set; } // looks fine butwon't get serialized
public string ID{ get; set; } // gets serialized
//..
public MyStripLine()
{
}
}
They can be added to the StripLines collections and work as expected. Unfortunately the Name property only looks good but doesn't get written out.. an while using another property (ID) is simple I can't get de-serialization to work.
Related
I want to serialize specific private fields that belongs to unmodifiable library class, how can i do that?
To serialize private fields, I know that I can modify settings to serialize all private fields, but I do not want to serialize all fields. Just the specific fields.
Also to serialize private fields, I know that JsonProperty or DataMember attribiutes can be written on the private fields. But I can not do that, because I can not modify library classes. Even if I can modify it, the library depends on an external library after modifying the library class (For example it depends on Newtonsoft.Json.dll if JsonProperty attribute is used).
So I tried to create a custom attribute to solve it.
// The class to serialize
public class FeatureRepository
{
// Serialize all private properties that belongs to this object.
public IDGenerator FeatureIDs;
// Do not serialize any private property that belongs to this object!
public IDGenerator oldFeatureIDs;
}
// Unmodifiable library class. Also it must be independent from any external libraries.
// There are some private fields but they do not seem.
public class IDGenerator
{
public IDGenerator();
public uint GenerateID();
public bool IsIDUsed(uint id);
public void ReleaseID(uint id);
public void UseID(uint id);
}
// Custom contract resolver
public class MyContractResolver : DefaultContractResolver
{
// Target properties
private List<SerializableJson> serializableJsons = new List<SerializableJson>();
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
JsonObjectContract contract = base.CreateObjectContract(objectType);
// Detect SerializableJsonAttribute and store the property info
foreach (JsonProperty property in contract.Properties)
{
IList<Attribute> attributes = property.AttributeProvider.GetAttributes(typeof(SerializableJsonAttribute), false);
foreach (SerializableJsonAttribute temp in attributes)
{
if (temp != null)
{
SerializableJson serializableJson = new SerializableJson()
{
PropertyType = property.PropertyType,
PropertyName = property.PropertyName
};
serializableJsons.Add(serializableJson);
}
}
}
return contract;
}
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
List<MemberInfo> members = base.GetSerializableMembers(objectType);
// It can compare just the type, can not compare the property name here.
SerializableJson serializableJson = serializableJsons.Where(temp => temp.PropertyType == objectType).FirstOrDefault();
// If the type is target type, add also private fields.
if (serializableJson != null)
{
IEnumerable<MemberInfo> nonPublicMembers = GetFieldsAndProperties(objectType, BindingFlags.NonPublic)
.Where(m => m is PropertyInfo p ? !IsIndexedProperty(p) : true);
foreach (MemberInfo member in nonPublicMembers)
members.Add(member);
}
return members;
}
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class, AllowMultiple = false)]
public class SerializableJsonAttribute : Attribute
{
}
public class SerializableJson
{
public Type PropertyType { get; set; }
public string PropertyName { get; set; }
}
// Finally use the custom attribute SerializableJson
// Both of them will serialized with theirs private fields.
public class FeatureRepository
{
[SerializableJson]
public IDGenerator FeatureIDs; // I want to serialize all private properties that belongs to this object.
public IDGenerator oldFeatureIDs; // I do not want to serialize any private property that belongs to this object!
}
How can I solve it?
Or is there any different way to solve this problem than the way I have tried?
Blockquote
Edit Let me explain the problem step by step: Firstly, FeatureRepository is serialized, SerializableJsonAttribute is searched for all properties. It finds the attribute on the FeatureIDs property but oldFeatureIDs. So it stores property type "IDGenerator" and property name "FeatureIDs". Secondly it starts to serialize IDGenerator. While serializing IDGenerator, you can not know that the serialization is done for "FeatureIDs" property or "oldFeatureIDs" property. If you would know, you can add private fields for "FeatureIDs" property.
https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm shows you can filter by property name.
ShouldSerialize can also be set using an IContractResolver. Conditionally serializing a property using an IContractResolver is useful if you don't want to place a ShouldSerialize method on a class or you didn't declare the class and are unable to.
The page contains full implementation:
(...)
if (property.DeclaringType == typeof(Employee) && property.PropertyName == "Manager")
{
property.ShouldSerialize = ...
(...)
I want to create a class with fixed properties and the capability to extend them as dynamic or ExpandoObject can.
e.g:
public class DynamicInstance : DynamicObject
{
public string FixedTestProperty { get; set; }
}
Usage:
DynamicInstance myCustomObj = new DynamicInstance();
myCustomObj.FixedTestProperty = "FixedTestValue";
myCustomObj.DynamicCreatedTestProperty = "Custom dynamic property value";
Finally if I serialize that class with json.net or something else output something like that:
{
FixedTestProperty: 'FixedTestValue',
DynamicCreatedTestProperty: 'Custom dynamic property value'
}
You need to inherit DynamicObject and override the TryGetMember and TrySetMember methods. Here is a class which has one property named One. However, you can add more to it dynamically.
public class ExpandOrNot : DynamicObject
{
public string One { get; set; }
// The inner dictionary.
Dictionary<string, object> dictionary
= new Dictionary<string, object>();
// This property returns the number of elements
// in the inner dictionary.
public int Count
{
get
{
return dictionary.Count;
}
}
// If you try to get a value of a property
// not defined in the class, this method is called.
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
string name = binder.Name.ToLower();
// If the property name is found in a dictionary,
// set the result parameter to the property value and return true.
// Otherwise, return false.
return dictionary.TryGetValue(name, out result);
}
// If you try to set a value of a property that is
// not defined in the class, this method is called.
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
dictionary[binder.Name.ToLower()] = value;
// You can always add a value to a dictionary,
// so this method always returns true.
return true;
}
}
Usage
dynamic exp = new ExpandOrNot { One = "1" };
exp.Two = "2";
More info here.
<== Fiddle Me ==>
This is possible using TrySetMember on DynamicObject.
The example at the bottom of this shows how to do it: https://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject.trysetmember(v=vs.110).aspx
I created some code from an XSD-schema-file using CodeDOM:
XmlSchemaImporter importer = new XmlSchemaImporter(schemas);
CodeNamespace code = new CodeNamespace(targetNamespace);
XmlCodeExporter exporter = new XmlCodeExporter(code);
foreach (XmlSchemaElement element in schema.Elements.Values)
{
XmlTypeMapping mapping = importer.ImportTypeMapping(element.QualifiedName);
exporter.ExportTypeMapping(mapping);
}
Now within my post-processing I realized that this code will generate properties like this:
bool prop1Field;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public bool Prop1
{
get
{
return prop1Field;
}
set
{
prop1Field = value;
}
}
But I want the generator to simply produce fields instead. Is there a way to achieve this? I know xsd.exe also produces fields when using the /f-argument.
EDIT: Afterwards I want to replace those by auto-properties. To do so with the current approach I´d have to delete the backing-field from the property and all its occurences within the generated code. If CodeDOM however generates a public field in the first place all I had to do is to delete this field, create a new property with the same name using CodeSnippedTypeMember as shown in this answer. Thus I won´t need to search the codes for occurences of the private backing-field and replace them by calls to the property.
No, there´s no option to be set to allow this as it shouldn´t matter for consuming code if your attribute is a field or a property (assuming we don´t use reflection) and public fields are considered bad practice.
Anyway I found a way to achieve this by copying all the comments and attributes of the property to the private backing-field and make the field public. However remember that doing so is mostly a bad design-idea.
public void Process(CodeNamespace code, XmlSchema schema)
{
foreach (var type in code.Types.Cast<CodeTypeDeclaration>().Where(x => !x.IsEnum))
{
var result = new List<CodeMemberField>();
var properties = type.Members.OfType<CodeMemberProperty>().ToList();
foreach (var property in properties)
{
ReplacePropertyByField(type, property);
}
}
}
private static void ReplacePropertyByField(CodeTypeDeclaration type, CodeMemberProperty property)
{
var backingField = GetBackingField(property, type);
backingField.Comments.AddRange(property.Comments);
backingField.Attributes = property.Attributes;
backingField.CustomAttributes = property.CustomAttributes;
backingField.Name = property.Name;
type.Members.Remove(property);
}
private static CodeMemberField GetBackingField(CodeMemberProperty property, CodeTypeDeclaration type)
{
var getterExpression = ((CodeMethodReturnStatement)property.GetStatements[0]).Expression;
var backingFieldName = ((CodeFieldReferenceExpression)getterExpression).FieldName;
return type.Members.OfType<CodeMemberField>().Single(x => x.Name == backingFieldName);
}
I am using a custom model binder to bind my complex type.
Here's the model:
[ModelBinder(typeof(SupplierModelBinder))]
public class SupplierModel
{
public string VendorId { get; set; }
public string VendorName { get; set; }
public override string ToString()
{
return VendorId;
}
}
Here's the binder:
public class SupplierModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
string key = bindingContext.ModelName;
ValueProviderResult val = bindingContext.ValueProvider.GetValue(key);
if (val != null)
{
string s = val.AttemptedValue as string;
if (s != null)
{
return new SupplierModel() { VendorId = s };
}
}
return null;
}
}
I'm rendering the model using Html.ActionLink calls the UserModel's ToString method. When that GETs to the server it uses the result of that to bind the model.
This works great (I'm not bothered about the VendorName property here), however it does rely on overriding ToString in the model class so I can use that value in the model binder.
How can I separate the binding/unbinding of my complex type from the ToString method?
I don't want to have to override ToString just so my model gets rendered correctly for my binder to interpret. For other types I'll have to (de)serialise to JSON or simiar, which I don't want to be in ToString.
I've managed to figure out how to do this without using a custom binding. The trick is realising that if I have things like Supplier.VendorId="XXXX" as part of the query string of the action link that gets rendered then it gets mapped correctly.
I used reflection to see what HtmlHelper.ActionLink() does when it's passed an object, which is that it creates an instance of RouteValueDictionary<string, object>, which creates a key for each property of the object.
This default implementation is close to what I want, but I need it to deal with properties of properties.
Luckly there's an overload of ActionLink() that takes a RouteValueDictionary<string, object> directly, so that left me with the problem of constructing one with the Property.SubProperty type keys correctly.
I ended up with the code below, which firstly uses the RouteValueDictonary's constructor to get a key for each property.
Then it removes any that won't get bound according to the Bind attribute (if the class has one), which tidies up the resulting querystring quite a bit.
The main part it does though is to look for any properties of type ...Model (the type name ending with "Model") and add that object's properties to the dictionary. I needed to use some rule for whether to recurse or not otherwise it would try and walk the properties of things like lists of objects etc. I figure that I'm already using a convention for my model classes, so I could stick to it.
public static RouteValueDictionary ToRouteValueDictionary(this object obj)
{
var Result = new RouteValueDictionary(obj);
// Find any ignored properties
var BindAttribute = (BindAttribute)obj.GetType().GetCustomAttributes(typeof(BindAttribute), true).SingleOrDefault();
var ExcludedProperties = new List<string>();
if (BindAttribute != null)
{
ExcludedProperties.AddRange(BindAttribute.Exclude.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
}
// Remove any ignored properties from the dictionary
foreach (var ExcludedProperty in ExcludedProperties)
{
Result.Remove(ExcludedProperty);
}
// Loop through each property, recursively adding sub-properties that end with "Model" to the dictionary
foreach (var Property in obj.GetType().GetProperties())
{
if (ExcludedProperties.Contains(Property.Name))
{
continue;
}
if (Property.PropertyType.Name.EndsWith("Model"))
{
Result.Remove(Property.Name);
var PropertyValue = Property.GetValue(obj, null);
if (PropertyValue != null)
{
var PropertyDictionary = PropertyValue.ToRouteValueDictionary();
foreach (var Key in PropertyDictionary.Keys)
{
Result.Add(string.Format("{0}.{1}", Property.Name, Key), PropertyDictionary[Key]);
}
}
}
}
return Result;
}
I have an attribute for validation called Required() : BaseAttribute, and I can track it using the following code: (The BaseAttribute just implements the IsValid() Method.)
public static String Validate(object Object)
{
StringBuilder builder = new StringBuilder();
if (Object != null)
{
Type ObjectType = Object.GetType();
PropertyInfo[] Properties = ObjectType.GetProperties();
foreach (PropertyInfo Property in Properties)
{
object[] Attributes = Property.GetCustomAttributes(typeof(BaseAttribute), true);
foreach (object Attribute in Attributes)
builder.AppendLine(((BaseAttribute)Attribute).IsValid(Property, Object));
}
}
return builder.ToString();
}
The problem is, this works:
class roh {
[Required()]
public string dah { get; set; }
}
class main {
Console.WriteLine(Validate(new roh()));
}
but this doesn't:
class fus {
private roh _roh
public roh Roh {
get { if (_roh == null)
_roh = new roh;
return _roh; }
set { _roh = value; }
}
}
class roh {
[Required()]
public string Dah { get; set; }
}
class main {
Console.WriteLine(Validate(new fus()));
}
How can I modify my Validate method so that it can recursively find the custom attributes no matter how deep the object is?
You could use the Microsoft Enterprise Library. It has some build in validation blocks (similar in style to what you're using here) and does support recursive object validation down the object graph, I believe.
You reference the EntLib Validation DLL, and can either use the built-in validation or write your own. You can then validate it using a simple Validation.Validate(myObject) call.
Hope that might help :)
You already said the magic word - recursion. For every property you visit, call Validate on the object it stores, and voila.
One caveat is infinite recursion - this will work OK if your object graph is a tree. If it's more complex, you'll need to track which objects you have already visited.