Search code examples
c#wcfserialization

WCF deserialization without known types


first, I hope I provide enough information to make things clear, if not please ask for more.

I have a working WCF communication using Duplex Channel and OneWay method calls. The ServiceHost is located inside a managed WPF application using NetPipeBinding, the client lives in a AppDomain inside that application. Everything is working as expected as long as all types are primitive (string, DateTime, ...) or specified as known type (List<object>, List<string>). But I need to send other types, for which I can´t add a known type attribute because I don´t know them at compile time.

As I read here (http://msdn.microsoft.com/library/ms731923(v=vs.100).aspx) all public types with public properties are supported, and so are types decorated with SerializableAttribute.

I tried to transfer a very simple class:

public class ADT
{
  public string Name { get; set; }
}

and as a second try

[Serializable]
public class SerializableADT
{
  public string Name { get; set; }
}

and as suggested by Herdo

[DataContract]
public class DataContractADT
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public object Value { get; set; }
}

but the deserialization fails for all three types.

There was an error while trying to serialize parameter _http://tempuri.org/:returnValue. The InnerException message was 'Type 'TestLibraries.SeriablizableADT' with data contract name 'SeriablizableADT:_http://schemas.datacontract.org/2004/07/TestLibraries' 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.'. Please see InnerException for more details.

How can I marshal any type that meets the MSDN rules (e.g. decorated with Serializable) without any compile time changes?


Solution

  • Warning: The NetDataContractSerializer poses a significant security risk and should be avoided.


    If this is a completely internal service, you can switch to using the `NetDataContractSerializer`, which solves this problem by including full type and assembly information (**note** this serializer completely breaks interoperability and contract versioning - so never use it for externally-exposed services). No `KnownType` necessary. To use it, you need a behavior and an attribute. You can place the following attribute on your contract or on a single operation:
    public class UseNetDataContractSerializerAttribute : Attribute, IOperationBehavior, IContractBehavior
    {
        void IOperationBehavior.ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
        {
            ReplaceDataContractSerializerOperationBehavior(description);
        }
    
        void IOperationBehavior.ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch)
        {
            ReplaceDataContractSerializerOperationBehavior(description);
        }
    
        void IOperationBehavior.Validate(OperationDescription description)
        {
        }
    
        void IOperationBehavior.AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
        {
        }
    
        private static void ReplaceDataContractSerializerOperationBehavior(OperationDescription description)
        {
            var behavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
    
            if (behavior != null)
            {
                description.Behaviors.Remove(behavior);
                description.Behaviors.Add(new NetDataContractSerializerOperationBehavior(description));
            }
        }
    
        void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
        {
            foreach (var operation in contractDescription.Operations)
            {
                ReplaceDataContractSerializerOperationBehavior(operation);
            }
        }
    
        void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            foreach (var operation in contractDescription.Operations)
            {
                ReplaceDataContractSerializerOperationBehavior(operation);
            }
        }
    
        void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
        {
        }
    
        void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }
    }
    
    public class NetDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
    {
        public NetDataContractSerializerOperationBehavior(OperationDescription operation)
            : base(operation)
        {
        }
    
        public NetDataContractSerializerOperationBehavior(OperationDescription operation, DataContractFormatAttribute dataContractFormatAttribute)
            : base(operation, dataContractFormatAttribute)
        {
        }
    
        public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
        {
            return new NetDataContractSerializer(name, ns);
        }
    
        public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
        {
            return new NetDataContractSerializer(name, ns);
        }
    }