XAML serialization of public get/private set properties - c#

I'm using System.Xaml.XamlServices.Save method to serialize an object which has properties with public getters/private setters and by design these properties are ignored. I tried to implement advice of how to override default XAML bindings and get private properties serialized, but it doesn't work for some reason - those properties are still ignored. Could anyone point out what's wrong:
public class CustomXamlSchemaContext : XamlSchemaContext
{
protected override XamlType GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments)
{
var type = base.GetXamlType(xamlNamespace, name, typeArguments);
return new CustomXamlType(type.UnderlyingType, type.SchemaContext, type.Invoker);
}
}
public class CustomXamlType : XamlType
{
public CustomXamlType(Type underlyingType, XamlSchemaContext schemaContext, XamlTypeInvoker invoker) : base(underlyingType, schemaContext, invoker)
{
}
protected override bool LookupIsConstructible()
{
return true;
}
protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck)
{
var member = base.LookupMember(name, skipReadOnlyCheck);
return new CustomXamlMember(member.Name, member.DeclaringType, member.IsAttachable);
}
}
public class CustomXamlMember : XamlMember
{
public CustomXamlMember(string name, XamlType declaringType, bool isAttachable) : base(name, declaringType, isAttachable)
{
}
protected override bool LookupIsReadOnly()
{
return false;
}
}
public static string Save(object instance)
{
var stringWriter1 = new StringWriter(CultureInfo.CurrentCulture);
var stringWriter2 = stringWriter1;
var settings = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true };
using (var writer = XmlWriter.Create(stringWriter2, settings))
{
Save(writer, instance);
}
return stringWriter1.ToString();
}
public static void Save(XmlWriter writer, object instance)
{
if (writer == null)
throw new ArgumentNullException("writer");
using (var xamlXmlWriter = new XamlXmlWriter(writer, new CustomXamlSchemaContext()))
{
XamlServices.Save(xamlXmlWriter, instance);
}
}
Having above infrastructure code and a class
public class Class1
{
public string Property1 { get; private set; }
public string Property2 { get; set; }
public DateTime AddedProperty { get; set; }
}
and serializing an instance of this class with
var obj = new Class1 { Property1 = "value1", Property2 = "value2" };
var objString = Save(obj);
I get the result
<Class1 AddedProperty="0001-01-01" Property2="value2" xmlns="clr-namespace:TestNamespace;assembly=Tests" />
where there is no entry for Property1.
What's even more interesting, that none of the overloads are called during serialization.

It turned out couple tweaks to my initial code solves the problem. Here's final solution:
private class CustomXamlSchemaContext : XamlSchemaContext
{
public override XamlType GetXamlType(Type type)
{
var xamlType = base.GetXamlType(type);
return new CustomXamlType(xamlType.UnderlyingType, xamlType.SchemaContext, xamlType.Invoker);
}
}
private class CustomXamlType : XamlType
{
public CustomXamlType(Type underlyingType, XamlSchemaContext schemaContext, XamlTypeInvoker invoker)
: base(underlyingType, schemaContext, invoker)
{
}
protected override bool LookupIsConstructible()
{
return true;
}
protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck)
{
var member = base.LookupMember(name, skipReadOnlyCheck);
return member == null ? null : new CustomXamlMember((PropertyInfo)member.UnderlyingMember, SchemaContext, member.Invoker);
}
protected override IEnumerable<XamlMember> LookupAllMembers()
{
foreach (var member in base.LookupAllMembers())
{
var value = new CustomXamlMember((PropertyInfo)member.UnderlyingMember, SchemaContext, member.Invoker);
yield return value;
}
}
protected override bool LookupIsPublic()
{
return true;
}
}
private class CustomXamlMember : XamlMember
{
public CustomXamlMember(PropertyInfo propertyInfo, XamlSchemaContext schemaContext, XamlMemberInvoker invoker)
: base(propertyInfo, schemaContext, invoker)
{
}
protected override bool LookupIsReadOnly()
{
return false;
}
protected override bool LookupIsWritePublic()
{
return true;
}
}
This customization allows to serialize/deserialize properties with public getter and public/internal/protected/private setters. It ignores all the other properties. It also serializes instances of internal classes.

The problem here is that you are attempting to write readonly and private properties.
According to the XAML standard the only readonly properties that are syntactically correct are for List, Dictionary and static members:
3.3.1.6. Only List, Dictionary, or Static Members may be Read-only
If neither [value type][is list] nor [value type][is dictionary], nor [is static] is True, [is read only] MUST be False.
Have a look here for MSDN syntax detail.
And the standard itself can be downloaded here.
You'll also note that only public properties have any relevance here (from msdn linked above):
In order to be set through attribute syntax, a property must be public and must be writeable. The value of the property in the backing type system must be a value type, or must be a reference type that can be instantiated or referenced by a XAML processor when accessing the relevant backing type.
For WPF XAML events, the event that is referenced as the attribute
name must be public and have a public delegate.
The property or event must be a member of the class or structure that
is instantiated by the containing object element.
and if you think about it you can see why.
The whole C# standard is really built around using classes that interact by using public properties and methods. By doing so other classes don't need to know what resides within a class beyond them. Each class can be treated as a black box where the public properties and methods are the class's interface to other code.
Here's an informative blog regarding XAML serialization.
Personally I would ask myself why I need to serialize/deserialize private member properties.

I'm not quite sure what is wrong with the above code, but if you like I can provide an alternative.
This is kind of a hacky way to do it, but it works (tested it). First, you can throw away all that custom XAML stuff. Then, just change your Class1 to be:
public class Class1
{
private string _Property1;
public string Property2 { get; set; }
public DateTime AddedProperty { get; set; }
public Class1()
{
}
public Class1(string prop1, string prop2)
{
_Property1 = prop1;
Property2 = prop2;
}
public string Property1
{
get { return _Property1; }
set { }
}
}
While the set accessor is accessible, it doesn't do anything so in effect this is the same as a public getter/private setter setup. Proper documentation will also help if someone else needs to use your Class1 and is wondering why the 'set' isn't working for Property1.
This could be a plan B in case no one posts a fix for your above code.
Update: If you need to deserialize the object as well, you could create another object that acts as a go-between for your Class1 and the serialization process. The whole setup would look like this:
public class Class1
{
public string Property1 { get; private set; }
public string Property2 { get; set; }
public DateTime AddedProperty { get; set; }
public Class1()
{
}
public Class1(string prop1, string prop2) : this()
{
Property1 = prop1;
Property2 = prop2;
}
public Class1(Class1DTO dto)
{
Property1 = dto.Property1;
}
public Class1DTO CreateDTO()
{
return new Class1DTO
{
AddedProperty = AddedProperty,
Property1 = Property1,
Property2 = Property2
};
}
}
public class Class1DTO
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public DateTime AddedProperty { get; set; }
}
The whole serialization/deserialization process would be like this:
var obj = new Class1("value1", "value2");
var dto = obj.CreateDTO();
var objString = Save(dto);
using (var stringReader = new StringReader(objString))
{
using (var reader = new XamlXmlReader(stringReader))
{
var deserializedDTO = XamlServices.Load(reader);
var originalObj = new Class1(dto);
}
}
You can then change access modifiers to fine tune the amount of access other people would have on your whole setup (you could create static Serialize/Deserialize methods on your Class1 type and push the Class1DTO type into a private nested class so people can't access it etc.).

Related

Creating a generic root object for JSON.NET? [duplicate]

Is there a way to change name of Data property during serialization, so I can reuse this class in my WEB Api.
For an example, if i am returning paged list of users, Data property should be serialized as "users", if i'm returning list of items, should be called "items", etc.
Is something like this possible:
public class PagedData
{
[JsonProperty(PropertyName = "Set from constructor")]??
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
EDIT:
I would like to have a control over this functionality, such as passing name to be used if possible. If my class is called UserDTO, I still want serialized property to be called Users, not UserDTOs.
Example
var usersPagedData = new PagedData("Users", params...);
You can do this with a custom ContractResolver. The resolver can look for a custom attribute which will signal that you want the name of the JSON property to be based on the class of the items in the enumerable. If the item class has another attribute on it specifying its plural name, that name will then be used for the enumerable property, otherwise the item class name itself will be pluralized and used as the enumerable property name. Below is the code you would need.
First let's define some custom attributes:
public class JsonPropertyNameBasedOnItemClassAttribute : Attribute
{
}
public class JsonPluralNameAttribute : Attribute
{
public string PluralName { get; set; }
public JsonPluralNameAttribute(string pluralName)
{
PluralName = pluralName;
}
}
And then the resolver:
public class CustomResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
if (prop.PropertyType.IsGenericType && member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type itemType = prop.PropertyType.GetGenericArguments().First();
JsonPluralNameAttribute att = itemType.GetCustomAttribute<JsonPluralNameAttribute>();
prop.PropertyName = att != null ? att.PluralName : Pluralize(itemType.Name);
}
return prop;
}
protected string Pluralize(string name)
{
if (name.EndsWith("y") && !name.EndsWith("ay") && !name.EndsWith("ey") && !name.EndsWith("oy") && !name.EndsWith("uy"))
return name.Substring(0, name.Length - 1) + "ies";
if (name.EndsWith("s"))
return name + "es";
return name + "s";
}
}
Now you can decorate the variably-named property in your PagedData<T> class with the [JsonPropertyNameBasedOnItemClass] attribute:
public class PagedData<T>
{
[JsonPropertyNameBasedOnItemClass]
public IEnumerable<T> Data { get; private set; }
...
}
And decorate your DTO classes with the [JsonPluralName] attribute:
[JsonPluralName("Users")]
public class UserDTO
{
...
}
[JsonPluralName("Items")]
public class ItemDTO
{
...
}
Finally, to serialize, create an instance of JsonSerializerSettings, set the ContractResolver property, and pass the settings to JsonConvert.SerializeObject like so:
JsonSerializerSettings settings = new JsonSerializerSettings
{
ContractResolver = new CustomResolver()
};
string json = JsonConvert.SerializeObject(pagedData, settings);
Fiddle: https://dotnetfiddle.net/GqKBnx
If you're using Web API (looks like you are), then you can install the custom resolver into the pipeline via the Register method of the WebApiConfig class (in the App_Start folder).
JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings;
settings.ContractResolver = new CustomResolver();
Another Approach
Another possible approach uses a custom JsonConverter to handle the serialization of the PagedData class specifically instead using the more general "resolver + attributes" approach presented above. The converter approach requires that there be another property on your PagedData class which specifies the JSON name to use for the enumerable Data property. You could either pass this name in the PagedData constructor or set it separately, as long as you do it before serialization time. The converter will look for that name and use it when writing out JSON for the enumerable property.
Here is the code for the converter:
public class PagedDataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(PagedData<>);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();
var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
string dataPropertyName = (string)type.GetProperty("DataPropertyName", bindingFlags).GetValue(value);
if (string.IsNullOrEmpty(dataPropertyName))
{
dataPropertyName = "Data";
}
JObject jo = new JObject();
jo.Add(dataPropertyName, JArray.FromObject(type.GetProperty("Data").GetValue(value)));
foreach (PropertyInfo prop in type.GetProperties().Where(p => !p.Name.StartsWith("Data")))
{
jo.Add(prop.Name, new JValue(prop.GetValue(value)));
}
jo.WriteTo(writer);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use this converter, first add a string property called DataPropertyName to your PagedData class (it can be private if you like), then add a [JsonConverter] attribute to the class to tie it to the converter:
[JsonConverter(typeof(PagedDataConverter))]
public class PagedData<T>
{
private string DataPropertyName { get; set; }
public IEnumerable<T> Data { get; private set; }
...
}
And that's it. As long as you've set the DataPropertyName property, it will be picked up by the converter on serialization.
Fiddle: https://dotnetfiddle.net/8E8fEE
UPD Sep 2020: #RyanHarlich pointed that proposed solution doesn't work out of the box. I found that Newtonsoft.Json doesn't initialize getter-only properties in newer versions, but I'm pretty sure it did ATM I wrote this answer in 2016 (no proofs, sorry :).
A quick-n-dirty solution is to add public setters to all properties ( example in dotnetfiddle ). I encourage you to find a better solution that keeps read-only interface for data objects. I haven't used .Net for 3 years, so cannot give you that solution myself, sorry :/
Another option with no need to play with json formatters or use string replacements - only inheritance and overriding (still not very nice solution, imo):
public class MyUser { }
public class MyItem { }
// you cannot use it out of the box, because it's abstract,
// i.e. only for what's intended [=implemented].
public abstract class PaginatedData<T>
{
// abstract, so you don't forget to override it in ancestors
public abstract IEnumerable<T> Data { get; }
public int Count { get; }
public int CurrentPage { get; }
public int Offset { get; }
public int RowsPerPage { get; }
public int? PreviousPage { get; }
public int? NextPage { get; }
}
// you specify class explicitly
// name is clear,.. still not clearer than PaginatedData<MyUser> though
public sealed class PaginatedUsers : PaginatedData<MyUser>
{
// explicit mapping - more agile than implicit name convension
[JsonProperty("Users")]
public override IEnumerable<MyUser> Data { get; }
}
public sealed class PaginatedItems : PaginatedData<MyItem>
{
[JsonProperty("Items")]
public override IEnumerable<MyItem> Data { get; }
}
Here is a solution that doesn't require any change in the way you use the Json serializer. In fact, it should also work with other serializers. It uses the cool DynamicObject class.
The usage is just like you wanted:
var usersPagedData = new PagedData<User>("Users");
....
public class PagedData<T> : DynamicObject
{
private string _name;
public PagedData(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
_name = name;
}
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
public override IEnumerable<string> GetDynamicMemberNames()
{
yield return _name;
foreach (var prop in GetType().GetProperties().Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.Name != nameof(Data)))
{
yield return prop.Name;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == _name)
{
result = Data;
return true;
}
return base.TryGetMember(binder, out result);
}
}
The following is another solution tested in .NET Standard 2.
public class PagedResult<T> where T : class
{
[JsonPropertyNameBasedOnItemClassAttribute]
public List<T> Results { get; set; }
[JsonProperty("count")]
public long Count { get; set; }
[JsonProperty("total_count")]
public long TotalCount { get; set; }
[JsonProperty("current_page")]
public long CurrentPage { get; set; }
[JsonProperty("per_page")]
public long PerPage { get; set; }
[JsonProperty("pages")]
public long Pages { get; set; }
}
I am using Humanizer for pluralization.
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (member.GetCustomAttribute<JsonPropertyNameBasedOnItemClassAttribute>() != null)
{
Type[] arguments = property.DeclaringType.GenericTypeArguments;
if(arguments.Length > 0)
{
string name = arguments[0].Name.ToString();
property.PropertyName = name.ToLower().Pluralize();
}
return property;
}
return base.CreateProperty(member, memberSerialization);
}
There's a package called SerializationInterceptor. Here's the GitHub link: https://github.com/Dorin-Mocan/SerializationInterceptor/wiki. You can also install the package using Nuget Package Manager.
The example from below uses Syste.Text.Json for serialization. You can use any other serializer(except Newtonsoft.Json). For more info on why Newtonsoft.Json not allowed, please refer to GitHub documentation.
You can create an interceptor
public class JsonPropertyNameInterceptorAttribute : InterceptorAttribute
{
public JsonPropertyNameInterceptorAttribute(string interceptorId)
: base(interceptorId, typeof(JsonPropertyNameAttribute))
{
}
protected override void Intercept(in AttributeParams originalAttributeParams, object context)
{
string theNameYouWant;
switch (InterceptorId)
{
case "some id":
theNameYouWant = (string)context;
break;
default:
return;
}
originalAttributeParams.ConstructorArgs.First().ArgValue = theNameYouWant;
}
}
And put the interceptor on the Data prop
public class PagedData<T>
{
[JsonPropertyNameInterceptor("some id")]
[JsonPropertyName("during serialization this value will be replaced with the one passed in context")]
public IEnumerable<T> Data { get; private set; }
public int Count { get; private set; }
public int CurrentPage { get; private set; }
public int Offset { get; private set; }
public int RowsPerPage { get; private set; }
public int? PreviousPage { get; private set; }
public int? NextPage { get; private set; }
}
And then you can serialize the object like this
var serializedObj = InterceptSerialization(
obj,
objType,
(o, t) =>
{
return JsonSerializer.Serialize(o, t, new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve });
},
context: "the name you want");
Hope this will be of use to you.
have a look here:
How to rename JSON key
Its not done during serialization but with a string operation.
Not very nice (in my eyes) but at least a possibility.
Cheers Thomas

Json.NET CamelCasePropertyNameContractResolver not working with private property setters

I am using Json.NET (8.0.3) and I am trying to use the CamelCasePropertyNameContractResolver with JsonConvert.DeseralizeObject() so that I can read JSON with camel case properties. Here is an example of the JSON.
{ "name":"somename", "type":"sometype" }
Here is the class I am trying to deserialize to:
public class MyClass {
public string Name { get; private set; }
public string Type { get; private set; }
}
If I use JsonConvert.DeseralizeObject the Name and Type values are null because technically the class property names do not match the JSON property names. This was expected. If I add the JsonProperty attribute then it will deserialize correctly (also expected).
public class MyClass {
[JsonProperty("name")]
public string Name { get; private set; }
[JsonProperty("type")]
public string Type { get; private set; }
}
I do not want to put the JsonProperty attribute on all of the properties so I tried the CamelCasePropertyNameContractResolver.
JsonConvert.DefaultSettings = () => new JsonSerialierSettings {
ContractResolver = new CamelCasePropertyNameContractResolver()
};
MyClass value = JsonConvert.DeserializeObject<MyClass>(json);
The Name and Type properties of the MyClass object are both null which was unexpected. If I make the setter public then it works correctly.
public class MyClass {
public string Name { get; set; }
public string Type { get; set; }
}
The obvious answer here is to just keep the setter public, but if I want/need to have the setter private, how can I get the CamelCasePropertyNameContractResolver to work with private setters? Am I doing something wrong, or is this a possible bug?
You can do it by writing a custom ContractResolver
string json = #"{""name"":""somename"", ""type"":""sometype"" }";
var settings = new JsonSerializerSettings() {
ContractResolver = new AllPropertiesContractResolver() };
var res = JsonConvert.DeserializeObject<MyClass>(json,settings);
public class MyClass
{
public string Name { get; private set; }
public string Type { get; private set; }
}
public class AllPropertiesContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var props = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.Select(x => new Newtonsoft.Json.Serialization.JsonProperty()
{
PropertyName = x.Name,
PropertyType = x.PropertyType,
Readable = true,
ValueProvider = new AllPropertiesValueProvider(x),
Writable = true
})
.ToList();
return props;
}
}
public class AllPropertiesValueProvider : Newtonsoft.Json.Serialization.IValueProvider
{
PropertyInfo _propertyInfo;
public AllPropertiesValueProvider(PropertyInfo p)
{
_propertyInfo = p;
}
public object GetValue(object target)
{
return _propertyInfo.GetValue(target); //Serialization
}
public void SetValue(object target, object value)
{
_propertyInfo.SetValue(target, value, null); //Deserialization
}
}
BTW: If I use JsonConvert.DeseralizeObject the Name and Type values are null because technically the class property names do not match the JSON property names. is not correct. If your properties had public setters and getters, deserialization would ignore the cases when using default settings (this is what I use in this answer. My ContractResolver additionally includes private properties in deserialization process; that is all).....
See the other question I used the same ContractResolver: What am I doing wrong with JSON.NET's JsonConvert

Dispaying and editing a sub property of a property in PropertyGrid

I could no longer find an exact solution to my problem in the internet so I'm asking this question. Hope you may be able to help me.
I have the following classes:
public Item
{
public FieldType MyField { get; set; }
public string Description { get; set; }
public int Capacity { get; set; }
}
public FieldType
{
public string Value { get; set; }
public string FieldCode { get; set; }
public string TableCode { get; set; }
}
In my form, I created an instance of Item class. Which contains the following members:
MyField (type of FieldType)
Description (type of string)
Capacity (an int)
Is it possible to only show the Value member of MyField property in the PropertyGrid?
Below is how I assign the selected object property of the PropertyGrid.
void Form1(object sender, EventArgs e)
{
propertyGrid1.SelectedObject = new Item();
}
Yes, easy:
add a computed read only property to Item
public Item
{
public FieldType MyField { get; set; }
public string MyFieldValue => MyField.Value;
public string Description { get; set; }
public int Capacity { get; set; }
}
Im not really sure of what you are looking for but here are 2 answers
1.(as I understood it)
if you want it to show only Value when you try and view the properties of a MyField Instance then all you need to do is add a constructor to the MyField so you can assign the other two values and change the public property to private like so
public FieldType
{
public string Value { get; set; }
private string FieldCode { get; set; }
private string TableCode { get; set; }
}
2.(this will hide the MyField from your propertyGrid)
Override the ToString() method of FielType
like so
public override string ToString()
{
return Value;
}
then set your MyField to private and encapsulate it. returning the instance as a string. which would use the overridden value.
like so
private FieldType MyField;
public string value{ get{return MyField.ToString();}set;}
your MyField will return the overridden ToString value which returns Value.
Solution 1 - Add a property
You can add a property to Item class to get and set MyField.Value:
public string Value
{
get
{
if (MyField != null)
return MyField.Value;
return null;
}
set
{
if (MyField != null)
MyField.Value = value;
}
}
• Preferably define that property in a partial class.
• Use this option when you have access to codes of the classes. If those classes are not yours, use 3rd solution.
Solution 2 - Use ExpandableObjectConverter
You can decorate the MyField property of Item class with ExpandableObjectConverter. Also decorate FieldType with [Browsable(false)] of FieldType class to hide it in property grid if you want:
[TypeConverter(typeof(ExpandableObjectConverter))]
public FieldType MyField { get; set; }
• To customize the text which is shown in front of MyField, you can override ToString method of FieldType and return Value. Also you can do it using a custom TypeConverter and overriding its ConvertTo method.
Solution 3 - Use a custom TypeDescriptor
It's not as easy as the first solution, but the output is completely like what you get using the first solution. It's suitable for cases that you can not manipulate those classes.
You can use it this way:
var item = new Item() { MyField = new FieldType() { Value = "Some Value" } };
TypeDescriptor.AddProvider(new MyTypeDescriptionProvider(), item);
this.propertyGrid1.SelectedObject = item;
Or by decorating Item class with:
[TypeDescriptionProvider(typeof(MyTypeDescriptionProvider))]
public class Item
Custom Property Descriptor
public class MyPropertyDescriptor : PropertyDescriptor
{
private PropertyDescriptor subProperty;
private PropertyDescriptor parentProperty;
public MyPropertyDescriptor(PropertyDescriptor parent, PropertyDescriptor sub)
: base(sub, null)
{
subProperty = sub;
parentProperty = parent;
}
public override bool IsReadOnly { get { return subProperty.IsReadOnly; } }
public override void ResetValue(object component)
{
subProperty.ResetValue(parentProperty.GetValue(component));
}
public override bool CanResetValue(object component)
{
return subProperty.CanResetValue(parentProperty.GetValue(component));
}
public override bool ShouldSerializeValue(object component)
{
return subProperty.ShouldSerializeValue(parentProperty.GetValue(component));
}
public override Type ComponentType { get { return parentProperty.ComponentType; } }
public override Type PropertyType { get { return subProperty.PropertyType; } }
public override object GetValue(object component)
{
return subProperty.GetValue(parentProperty.GetValue(component));
}
public override void SetValue(object component, object value)
{
subProperty.SetValue(parentProperty.GetValue(component), value);
OnValueChanged(component, EventArgs.Empty);
}
}
Custom type Descriptor
public class MyTypeDescriptor : CustomTypeDescriptor
{
ICustomTypeDescriptor original;
public MyTypeDescriptor(ICustomTypeDescriptor originalDescriptor)
: base(originalDescriptor)
{
original = originalDescriptor;
}
public override PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(new Attribute[] { });
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = original.GetProperties().Cast<PropertyDescriptor>().ToList();
var parent = properties.Where(x => x.Name == "MyField").First();
var sub = TypeDescriptor.GetProperties(typeof(FieldType))["Value"];
properties.Remove(parent);
properties.Add(new MyPropertyDescriptor(parent, sub));
return new PropertyDescriptorCollection(properties.ToArray());
}
}
Custom TypeDescriptorProvider
public class MyTypeDescriptionProvider : TypeDescriptionProvider
{
public MyTypeDescriptionProvider()
: base(TypeDescriptor.GetProvider(typeof(object))) { }
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
object instance)
{
ICustomTypeDescriptor baseDes = base.GetTypeDescriptor(objectType, instance);
return new MyTypeDescriptor(baseDes);
}
}
• Use this option if Item and FieldType are not yours. If those classes are yours and you can change their code, use first solution.

How to dynamically choose what inherited properties will be serialized?

I have a base class with a few properties, and three deriving classes.
I want to serialize an object containing all three derived classes, but each derived class should expose a different set of properties from base class.
I want to do this dynamically with XmlAttributeOverrides and have tried a few differnt ways to do that, but nothing that really does it.
[Serializable]
public class A
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
[Serializable]
public class B : A
{
}
[Serializable]
public class C : A
{
}
[Serializable]
public class Container
{
public B B { get; set; }
public C C { get; set; }
}
class Program
{
static void Main(string[] args)
{
MemoryStream memoryStream = new MemoryStream();
StreamWriter encodingWriter = new StreamWriter(memoryStream, Encoding.Unicode);
var xmlWriter = XmlWriter.Create(encodingWriter, new XmlWriterSettings
{
Indent = false,
OmitXmlDeclaration = true,
});
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attribute = new XmlAttributes();
attribute.XmlIgnore = true;
overrides.Add(typeof(B), "Property1", attribute);
overrides.Add(typeof(C), "Property2", attribute);
var container = new Container
{
B = new B {Property1 = "B property 1", Property2 = "B property 2"},
C = new C {Property1 = "C property 1", Property2 = "C property 2"}
};
var xmlSerializer = new XmlSerializer(typeof(Container), overrides);
xmlSerializer.Serialize(xmlWriter, container);
var result = Encoding.Unicode.GetString(memoryStream.ToArray());
}
}
In the above code, the result string will contain all the properties of A in B and C, but I really want it just to contain B Property2 and C Property1 (as I have set the XmlIgnore attributes for them).
How do I do this?
EDIT: The expected XML:
<Container xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B><Property2>B property 2</Property2></B><C><Property1>C property 1</Property1></C></Container>
The actual XML:
<Container xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B><Property1>B property 1</Property1><Property2>B property 2</Property2></B><C><Property1>C property 1</Property1><Property2>C property 2</Property2></C></Container>
EDIT 2: The above is an example to visualize the problem, but I will expand on why we have the need to do this.
We have a Container-class (like above) which contains different kinds of derived objects (like above).
When we expose data from the Container-class to others, we want to be able to expose just certain fields which are configurable at some other place (might be sensitive data or whatnot).
This we do with help of XmlAttributeOverrides to set XmlIgnore property for the exposed properties. This works well for most types of objects (which does not have inheritance), but now we have the need to serialize different derived objects in different ways (configurable).
So in the above example, some customer has made the decision to exclude Property1 from class B and Property2 from class C, and as such I want the XML to look like above.
This did not work with the above code though; it seems like XmlSerializer uses the settings for the properties from the base class A, instead of using it from the respective derived classes B and C.
It's hard to tell from your question exactly what XML output you're looking for, so I'll just throw out an example and you can modify it as you need to. (EDIT: Seems I lucked out; the sample implementations below match your edited desired XML result)
You can use the little known ShouldSerializePROPERTYNAME method to dynamically instruct the XmlSerializer to ignore properties. For example:
public class A
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public virtual bool ShouldSerializeProperty1()
{
return true;
}
public virtual bool ShouldSerializeProperty2()
{
return true;
}
}
These methods can then be overridden by the subclasses to ignore those properties:
public class B : A
{
public override bool ShouldSerializeProperty1()
{
return false;
}
}
public class C : A
{
public override bool ShouldSerializeProperty2()
{
return false;
}
}
Similarly, you can control the return values of the methods via other properties which can be assigned by Container:
public class A
{
public string Property1 { get; set; }
public string Property2 { get; set; }
[XmlIgnore]
internal bool _ShouldSerializeProperty1 = true;
[XmlIgnore]
internal bool _ShouldSerializeProperty2 = true;
public bool ShouldSerializeProperty1()
{
return _ShouldSerializeProperty1;
}
public bool ShouldSerializeProperty2()
{
return _ShouldSerializeProperty2;
}
}
Then when assigning B and C to Container, you can set those flags:
public class Container
{
private B _B;
public B B
{
get
{
return _B;
}
set
{
if (value != null)
{
value._ShouldSerializeProperty1 = false;
value._ShouldSerializeProperty2 = true;
}
_B = value;
}
}
private C _C;
public C C
{
get
{
return _C;
}
set
{
if (value != null)
{
value._ShouldSerializeProperty1 = true;
value._ShouldSerializeProperty2 = false;
}
_C = value;
}
}
}
These are just some examples (and I don't claim to have used best practices here) to demonstrate how ShouldSerialize can be used. You'll probably want to adapt it however best to your particular usage.
EDIT: Given your updated post, there's another possibility but requires a bit of extra work defining your subclasses and some DRY violation (though for serialization, sometimes that's ok).
First define A's properties as virtual and override them in the subclasses as basic wrappers:
public class A
{
public virtual string Property1 { get; set; }
public virtual string Property2 { get; set; }
}
public class B : A
{
public override string Property1 { get { return base.Property1; } set { base.Property1 = value; } }
public override string Property2 { get { return base.Property2; } set { base.Property2 = value; } }
}
public class C : A
{
public override string Property1 { get { return base.Property1; } set { base.Property1 = value; } }
public override string Property2 { get { return base.Property2; } set { base.Property2 = value; } }
}
Then since (I'm assuming) you're managing/building your XmlSerializer serialization via those configurations, include XmlIgnore overrides for all of the base class's properties:
overrides.Add(typeof(A), "Property1", attribute);
overrides.Add(typeof(A), "Property2", attribute);
Then also include XmlIgnore overrides for the subclass properties that you wish to truly ignore:
overrides.Add(typeof(B), "Property2", attribute);
overrides.Add(typeof(C), "Property1", attribute);
This will produce your desired output.

C# - copying property values from one instance to another, different classes

I have two C# classes that have many of the same properties (by name and type). I want to be able to copy all non-null values from an instance of Defect into an instance of DefectViewModel. I was hoping to do it with reflection, using GetType().GetProperties(). I tried the following:
var defect = new Defect();
var defectViewModel = new DefectViewModel();
PropertyInfo[] defectProperties = defect.GetType().GetProperties();
IEnumerable<string> viewModelPropertyNames =
defectViewModel.GetType().GetProperties().Select(property => property.Name);
IEnumerable<PropertyInfo> propertiesToCopy =
defectProperties.Where(defectProperty =>
viewModelPropertyNames.Contains(defectProperty.Name)
);
foreach (PropertyInfo defectProperty in propertiesToCopy)
{
var defectValue = defectProperty.GetValue(defect, null) as string;
if (null == defectValue)
{
continue;
}
// "System.Reflection.TargetException: Object does not match target type":
defectProperty.SetValue(viewModel, defectValue, null);
}
What would be the best way to do this? Should I maintain separate lists of Defect properties and DefectViewModel properties so that I can do viewModelProperty.SetValue(viewModel, defectValue, null)?
Edit: thanks to both Jordão's and Dave's answers, I chose AutoMapper. DefectViewModel is in a WPF application, so I added the following App constructor:
public App()
{
Mapper.CreateMap<Defect, DefectViewModel>()
.ForMember("PropertyOnlyInViewModel", options => options.Ignore())
.ForMember("AnotherPropertyOnlyInViewModel", options => options.Ignore())
.ForAllMembers(memberConfigExpr =>
memberConfigExpr.Condition(resContext =>
resContext.SourceType.Equals(typeof(string)) &&
!resContext.IsSourceValueNull
)
);
}
Then, instead of all that PropertyInfo business, I just have the following line:
var defect = new Defect();
var defectViewModel = new DefectViewModel();
Mapper.Map<Defect, DefectViewModel>(defect, defectViewModel);
Take a look at AutoMapper.
There are frameworks for this, the one I know of is Automapper:
http://automapper.codeplex.com/
http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/01/22/automapper-the-object-object-mapper.aspx
Replace your erroneous line with this:
PropertyInfo targetProperty = defectViewModel.GetType().GetProperty(defectProperty.Name);
targetProperty.SetValue(viewModel, defectValue, null);
Your posted code is attempting to set a Defect-tied property on a DefectViewModel object.
In terms of organizing the code, if you don't want an external library like AutoMapper, you can use a mixin-like scheme to separate the code out like this:
class Program {
static void Main(string[] args) {
var d = new Defect() { Category = "bug", Status = "open" };
var m = new DefectViewModel();
m.CopyPropertiesFrom(d);
Console.WriteLine("{0}, {1}", m.Category, m.Status);
}
}
// compositions
class Defect : MPropertyGettable {
public string Category { get; set; }
public string Status { get; set; }
// ...
}
class DefectViewModel : MPropertySettable {
public string Category { get; set; }
public string Status { get; set; }
// ...
}
// quasi-mixins
public interface MPropertyEnumerable { }
public static class PropertyEnumerable {
public static IEnumerable<string> GetProperties(this MPropertyEnumerable self) {
return self.GetType().GetProperties().Select(property => property.Name);
}
}
public interface MPropertyGettable : MPropertyEnumerable { }
public static class PropertyGettable {
public static object GetValue(this MPropertyGettable self, string name) {
return self.GetType().GetProperty(name).GetValue(self, null);
}
}
public interface MPropertySettable : MPropertyEnumerable { }
public static class PropertySettable {
public static void SetValue<T>(this MPropertySettable self, string name, T value) {
self.GetType().GetProperty(name).SetValue(self, value, null);
}
public static void CopyPropertiesFrom(this MPropertySettable self, MPropertyGettable other) {
self.GetProperties().Intersect(other.GetProperties()).ToList().ForEach(
property => self.SetValue(property, other.GetValue(property)));
}
}
This way, all the code to achieve the property-copying is separate from the classes that use it. You just need to reference the mixins in their interface list.
Note that this is not as robust or flexible as AutoMapper, because you might want to copy properties with different names or just some sub-set of the properties. Or it might downright fail if the properties don't provide the necessary getters or setters or their types differ. But, it still might be enough for your purposes.
This is cheap and easy. It makes use of System.Web.Script.Serialization and some extention methods for ease of use:
public static class JSONExts
{
public static string ToJSON(this object o)
{
var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
return oSerializer.Serialize(o);
}
public static List<T> FromJSONToListOf<T>(this string jsonString)
{
var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
return oSerializer.Deserialize<List<T>>(jsonString);
}
public static T FromJSONTo<T>(this string jsonString)
{
var oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
return oSerializer.Deserialize<T>(jsonString);
}
public static T1 ConvertViaJSON<T1>(this object o)
{
return o.ToJSON().FromJSONTo<T1>();
}
}
Here's some similiar but different classes:
public class Member
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsCitizen { get; set; }
public DateTime? Birthday { get; set; }
public string PetName { get; set; }
public int PetAge { get; set; }
public bool IsUgly { get; set; }
}
public class MemberV2
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsCitizen { get; set; }
public DateTime? Birthday { get; set; }
public string ChildName { get; set; }
public int ChildAge { get; set; }
public bool IsCute { get; set; }
}
And here's the methods in action:
var memberClass1Obj = new Member {
Name = "Steve Smith",
Age = 25,
IsCitizen = true,
Birthday = DateTime.Now.AddYears(-30),
PetName = "Rosco",
PetAge = 4,
IsUgly = true,
};
string br = "<br /><br />";
Response.Write(memberClass1Obj.ToJSON() + br); // just to show the JSON
var memberClass2Obj = memberClass1Obj.ConvertViaJSON<MemberV2>();
Response.Write(memberClass2Obj.ToJSON()); // valid fields are filled
For one thing I would not place that code (somewhere) external but in the constructor of the ViewModel:
class DefectViewModel
{
public DefectViewModel(Defect source) { ... }
}
And if this is the only class (or one of a few) I would not automate it further but write out the property assignments. Automating it looks nice but there may be more exceptions and special cases than you expect.
Any chance you could have both classes implement an interface that defines the shared properties?

Categories

Resources