DataContract serialization of property of "Type"
Asked Answered
A

3

11

How can I effectively serialize a property of type "Type" in my DataContract attributed class? I'm assuming that Type is a non-serializable Type (wow that's getting silly sounding.) I'm sure that there is a way to do this that meets my needs. Basically I need to serialize the name of a type for a factory method to effectively construct, but I don't want to expose it as a string, I want a Type.

I know there are a number of ways to do this, I'm curious what other methods are known at this time.

EDIT: I just realized it may be something else causing it but here is the error, and below I have the class definition.

Type 'System.RuntimeType' with data contract name 'RuntimeType:http://schemas.datacontract.org/2004/07/System' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.

[DataContract]
public class PlottingDeviceInfo : ObservableObject
{
    private string _deviceName;
    [DataMember]
    public string DeviceName
    {
        get
        {
            return _deviceName;
        }
        set
        {
            Set(() => DeviceName, ref _deviceName, value);
        }
    }

    private Type _deviceType;
    [DataMember]
    public Type DeviceType
    {
        get
        {
            return _deviceType;
        }
        set
        {
            Set(() => DeviceType, ref _deviceType, value);
        }
    }

    private DeviceSettingsInfo _settings;
    [DataMember]
    public DeviceSettingsInfo Settings
    {
        get
        {
            return _settings;
        }
        set
        {
            Set(() => Settings, ref _settings, value);
        }
    }

    private DeviceChannelInfo _channel;
    [DataMember]
    public DeviceChannelInfo Channel
    {
        get
        {
            return _channel;
        }
        set
        {
            Set(() => Channel, ref _channel, value);
        }
    }

    private DeviceCategory _deviceCategory;
    [IgnoreDataMember]
    public DeviceCategory DeviceCategory
    {
        get
        {
            return _deviceCategory;
        }
        set
        {
            Set(() => DeviceCategory, ref _deviceCategory, value);
        }
    }
}

Here is the base class, used to add observability for viewmodel consumption.

[DataContract]
public class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    [IgnoreDataMember]
    protected PropertyChangedEventHandler PropertyChangedHandler
    {
        get
        {
            return PropertyChanged;
        }
    }

    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public void VerifyPropertyName(string propertyName)
    {
        var myType = this.GetType();
        if (!string.IsNullOrEmpty(propertyName)
            && myType.GetProperty(propertyName) == null)
        {
            throw new ArgumentException("Property not found", propertyName);
        }
    }

    protected virtual void RaisePropertyChanged(string propertyName)
    {
        VerifyPropertyName(propertyName);

        var handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    protected virtual void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
    {
        if (propertyExpression == null)
        {
            return;
        }

        var handler = PropertyChanged;

        if (handler != null)
        {
            var body = propertyExpression.Body as MemberExpression;
            handler(this, new PropertyChangedEventArgs(body.Member.Name));
        }
    }

    protected void Set<T>(
        Expression<Func<T>> propertyExpression,
        ref T field,
        T newValue)
    {
        if (EqualityComparer<T>.Default.Equals(field, newValue))
        {
            return;
        }

        field = newValue;
        RaisePropertyChanged(propertyExpression);
    }

    protected void Set<T>(
        string propertyName,
        ref T field,
        T newValue)
    {
        if (EqualityComparer<T>.Default.Equals(field, newValue))
        {
            return;
        }

        field = newValue;
        RaisePropertyChanged(propertyName);
    }
}
Abercrombie answered 20/9, 2011 at 16:26 Comment(1)
Edited with serialization error.Abercrombie
P
12

Type cannot be expressed in a cross-platform way, so it has no inbuilt representation. Your best bet is to represent it as a string, i.e.

public Type DeviceType { get; set; }
[DataMember(Name="DeviceType")]
private string DeviceTypeName {
    get { return DeviceType == null ? null : DeviceType.AssemblyQualifiedName; }
    set { DeviceType = value == null ? null : Type.GetType(value); }
}
Phoebephoebus answered 20/9, 2011 at 17:4 Comment(3)
Mark: I'm surprised you'd say this. DataContractSerializer in .NET 3.5SP1 has support for ISerializable implementations...and System.RuntimeType implements ISerializable. Adding a KnownType should be sufficient. That said, serializing an AssemblyQualified name instead of using the builtin ISerializable implementation isn't necessarily wrong.Cherenkov
@JeffN825 that depends entirely on what you want to see on the wire. If ISerializable was added... that is interesting but... meh - still not sure that makes it desirable in terms of what goes on the wire.Phoebephoebus
Agreed. Far from desirable, but nonetheless, Type (RuntimeType) CAN be serialized over the wireCherenkov
T
0

To the root class, add the KnownType attribute with System.RuntimeType passed in as a type.

In C#,

[KnownType(typeof(System.RuntimeType))]
Toein answered 20/9, 2011 at 16:36 Comment(8)
What is a "Root class" do you mean the base class? or do you mean the class I am serializing.Abercrombie
The class you are serializing.Toein
erm... no such type... do you mean System.Dynamic.DynamicMetaObject.RuntimeType?Abercrombie
which type? Type DeviceType {get;set;} I'm setting it explicitly in external code... are we on totally different pages?Abercrombie
Yea. How are you setting that property?Toein
from which end? serialization? or programatically?Abercrombie
O_O a type! what does it matter, I'm using an explicitly specified type from my own assembly.Abercrombie
it is not public so you cannot add due to the protection level.Flambeau
L
0

What you can also do, especially if you do not want to change PlottingDeviceInfo, is to pass a IDataContractSurrogate in the constructor of DataContractSerializer. What I did (don' know if there is an easier way) is to define

public class TypeWrapper
{
    public string TypeName;
}

and then use it like this:

public class TypeReplacementSurrogate : IDataContractSurrogate
{
    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null;
    }

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        return null;
    }

    public Type GetDataContractType(Type type)
    {
        if(type == typeof(Type))
        {
            return typeof (TypeWrapper);
        }

        return type;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        var objAsTypeWrapper = obj as TypeWrapper;
        if (objAsTypeWrapper != null)
        {
            return Assembly.GetExecutingAssembly().GetType(objAsTypeWrapper.TypeName);
        }

        return obj;
    }


    public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
    {
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        var objAsType = obj as Type;
        if (objAsType != null)
        {
            return new TypeWrapper() {TypeName = objAsType.FullName};
        }

        return obj;
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null;
    }

    public System.CodeDom.CodeTypeDeclaration ProcessImportedType(
        System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
    {
        return null;
    }
}
Lippmann answered 11/4, 2013 at 15:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.