0

I'm facing a strange issue with WCF serialization. My project has a type named RefEntity which has a property of another type named RefDomain. A WCF service defines two methods, one takes type RefEntity as parameter, and another takes type RefDomain as parameter.

The method with the RefDomain parameter doesn't seem to serialize the object as all the properties have null value on the service side. Whereas, the method with RefEntity parameter (and RefDomain as a member) works as expected (RefDomain member is serialized as expected).

What am I missing here?

public class RefDomain
{
    private string name;
    private RefDomain parent;
    private Type entityType;

    public string Name
    {
        get => this.name;
        protected set => this.name = value;
    }

    public RefDomain Parent
    {
          get => this.parent;
          protected set => this.parent = value;
    }

    public Type RefEntityType
    {
          get => this.entityType;
          protected set => this.entityType = value;
    }
}
  • Your properties are all `protected`, how are they ever set to begin with? – dbc Dec 11 '22 at 17:47
  • Can you [edit] your question to show how an instance of `RefDomain` is constructed and serialized? – dbc Dec 11 '22 at 17:55
  • And if `RefDomain` can be serialized when referred to by `RefEntity`, can you share a [mcve] showing that as well? – dbc Dec 11 '22 at 18:17
  • Is it only `RefEntityType` that is not serialized? Is it useful to change the parameter type? – Jiayao Dec 12 '22 at 09:15
  • @Jiayao - As described in the question, my service has a method which takes `RefDomain` type as parameter, and nothing is serialized. But another method which takes `RefEntity` as parameter, and has `RefDomain` type as one of its members, everything is serialized as expected. – Santhosh Shanmugam Dec 12 '22 at 13:54

2 Answers2

1

Your properties have protected setters, and DataContractSerializer will only serialize properties that are fully public, unless marked with data contract attributes.

Once you apply the necessary [DataContract] and [DataMember] attributes, you will discover a second problem, namely that DataContractSerializer cannot serialize values of type System.Type. (Demo fiddle #1 here). To serialize your Type RefEntityType property, you will need to use some sort of surrogate for Type that can be serialized, for instance a string.

Thus the following version of RefDomain can be serialized via the data contract serializer:

public class RefDomain
{
    private string name;
    private RefDomain parent;
    private Type entityType;

    [DataMember]
    public string Name
    {
        get => this.name;
        protected set => this.name = value;
    }

    [DataMember]
    public RefDomain Parent
    {
          get => this.parent;
          protected set => this.parent = value;
    }

    public Type RefEntityType
    {
          get => this.entityType;
          protected set => this.entityType = value;
    }

    [DataMember(Name = "RefEntityType")]
    string RefEntityTypeString 
    {
        get
        {
            return RefEntityType?.FullName;
        }
        set
        {
            // Note that simply deserializing a type supplied over the wire will make your application vulnerable to type injection attacks,
            // in which an attacker tricks you into constructing a type that effects an attack when constructed, deserialized or disposed.  
            // This is a known vulnerability with Json.NET's TypeNameHandling.None.  See for details
            // https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf
            // https://stackoverflow.com/questions/39565954/typenamehandling-caution-in-newtonsoft-json/
            // https://stackoverflow.com/questions/49038055/external-json-vulnerable-because-of-json-net-typenamehandling-auto
            var type = value == null ? null : Type.GetType(value);
            // Check the type against a whitelist here?
            RefEntityType = type;
        }
    }
    
    // Your class was had no way to set the protected properties, so I added a parameterized constructor.
    public RefDomain() { }
    public RefDomain(string name, RefDomain parent, Type entityType) => (this.name, this.parent, this.entityType) = (name, parent, entityType);
}

Notes:

  1. Deserializing a type supplied over the wire will make your application vulnerable to attack gadget deserialization attacks, in which an attacker tricks your app into constructing a type that effects an attack when constructed, deserialized or disposed. This is a known vulnerability with Json.NET's TypeNameHandling. See for details:

    If your app ever constructs an instance of your RefEntityType, you app may become vulnerable to such attacks. To prevent this, you could check the deserialized type against a whitelist of allowed types.

  2. In your question, you ask why the partially public properties of RefDomain are serialized when it is serialized as a member of some other root object RefEntity. There is not enough information in your question to explain why this is happening. Perhaps the endpoint where the RefEntity is serialized is using a has subclassed RefDomain, or uses a serialization surrogate, or has set DataContractSerializerSettings.SerializeReadOnlyTypes = true.

    But regardless of why partially public properties RefDomain properties are serialized when inside RefEntity, if you want those properties serialized in all contexts, it is correct and sufficient to mark RefDomain with [DataContract] and the properties with [DataMember]

Demo fiddle #2 here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • If protected setters is the issue, then how come the same type is serialized with `RefEntity` type? Allow me some time and I will share the complete code – Santhosh Shanmugam Dec 12 '22 at 03:57
  • 1
    @SanthoshShanmugam - here is a demo of serializing your `RefDomain` without data contract attributes: https://dotnetfiddle.net/1JKjI6. As you can see, nothing is serialized. – dbc Dec 12 '22 at 04:01
  • You're right @dbc! When I modified the `RefDomain` class with public setters, WCF is able to serialize the type, and now the issue is with the System.Type member. But I fail to understand is how is this working with the `RefEntity` type. Let me share a working example. – Santhosh Shanmugam Dec 12 '22 at 04:46
  • I'm unable to reproduce the issue with [.NET Fiddle](https://dotnetfiddle.net/K4s5zS) (not sure why). But you can look at the code that is in my codebase. Is there a way we can connect offline and I will demo the issue to you? – Santhosh Shanmugam Dec 12 '22 at 05:22
  • Any chance you're using [dynamic proxies](https://learn.microsoft.com/en-us/ef/ef6/fundamentals/proxies)? – dbc Dec 12 '22 at 05:36
  • Nope, not using dynamic proxies – Santhosh Shanmugam Dec 12 '22 at 05:38
  • @SanthoshShanmugam - absent a [mcve] it's going to be hard for us to guess why serializing of `RefDomain` works when a member of `RefEntity`. Maybe the `RefEntity` method uses a [serialization surrogate](https://learn.microsoft.com/en-us/dotnet/framework/wcf/extending/data-contract-surrogates). Maybe the `RefEntity` method sets [`DataContractSerializerSettings.SerializeReadOnlyTypes`](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datacontractserializersettings.serializereadonlytypes) somewhere. – dbc Dec 12 '22 at 16:32
  • 1
    @SanthoshShanmugam - but regardless of why get-only properties `RefDomain ` properties are serialized when inside `RefEntity`, if you want those properties serialized in all contexts, it is sufficient to mark them with `[DataMember`]. – dbc Dec 12 '22 at 16:34
0

As far as I know, user-defined types used in wcf need to be tagged with the Data Contract attribute, and members need to be tagged with the Data Member attribute.

You can either mark RefDomain as a Data Contract attribute or create a separate class to hold data from RefDomain and pass it through wcf.

Jiayao
  • 510
  • 3
  • 7