Pondidum
Pondidum

Reputation: 11627

DataContractSerializer for custom Value types

I have a WCF service which exposes a type TestTypeOne which currently has a string property called ProductId:

public class TestTypeOne
{
    [DataMember(Name = "ProductId")]
    public string ProductId { get; set; } //note it's a string at the moment
}

I want to change the type of this property from string to a custom value type called ProductId, but without breaking the WCF contract (this is only for the server side, the clients should see the ProductId as a string still.)

public class TestTypeOne
{
    [DataMember(Name = "ProductId")]
    public ProductId ProductId { get; set; }
}

The custom type is something like this (most code removed for brevity):

public struct ProductId : IEquatable<ProductId>
{
    readonly string productId;

    public ProductId(string productId)
    {
        this.productId = productId
    }

    public override string ToString() => productId ?? string.Empty;
}

Using the following test code:

var sb = new StringBuilder();
using (var writer = new XmlTextWriter(new StringWriter(sb)))
{
    var dto = new TestTypeOne {
        ProductId = new ProductId("1234567890123")
    };

    var serializer = new DataContractSerializer(typeof(TestTypeOne));
    serializer.WriteObject(writer, dto);
    writer.Flush();
}

Console.WriteLine(sb.ToString());

The expected output when serializing should be:

<Scratch.TestTypeOne xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Tests">
    <ProductId>1234567890123</ProductId>
</Scratch.TestTypeOne>

I have tried implementing ISerializable but that seems to only let me control the content of the ProductId xml tag, not the tag itself (so I can make things like <ProductId><something>1234113</something></ProductId> happen).

Ideally I am after something I can do to the ProductId type itself, as this type is used in many places, and many contracts.

Upvotes: 1

Views: 778

Answers (2)

Evk
Evk

Reputation: 101473

I think easiest way would be to implement IXmlSerializable:

public struct ProductId : IXmlSerializable
{
    readonly string productId;

    public ProductId(string productId)
    {
        this.productId = productId;
    }

    public override string ToString() => productId ?? string.Empty;

    XmlSchema IXmlSerializable.GetSchema() {
        return null;
    }

    void IXmlSerializable.ReadXml(XmlReader reader) {
        this = new ProductId(reader.ReadString());
    }

    void IXmlSerializable.WriteXml(XmlWriter writer) {
        writer.WriteString(this.productId);
    }
}

To adjust WCF xsd generation for this case (force it to generate xs:string) - you can use data contract surrogate for xsd generation. For example you can have such suggorate:

public class ProductIdSurrogate : IDataContractSurrogate {
    public Type GetDataContractType(Type type) {
        if (type == typeof(ProductId))
            return typeof(string);
        return type;
    }

    public object GetObjectToSerialize(object obj, Type targetType) {
        throw new NotImplementedException();
    }

    public object GetDeserializedObject(object obj, Type targetType) {
        throw new NotImplementedException();
    }

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

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

    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes) {
        throw new NotImplementedException();
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData) {
        throw new NotImplementedException();
    }

    public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit) {
        throw new NotImplementedException();
    }
}

Whose only purpose is say that data contract for ProductId type is really string.

Then you can use this surrogate for schema generation:

var exporter = new XsdDataContractExporter();
exporter.Options = new ExportOptions();
exporter.Options.DataContractSurrogate = new ProductIdSurrogate();
exporter.Export(typeof(TestTypeOne));

You can use this approach for serialization itself, but I find it more complicated.

You can read more info about surrogates and WCF here, and the very bottom there is an example of how you can use surrogate for WSDL generation endpoint (section "To Use a surrogate for Metadata Export").

Upvotes: 2

Ozkar619
Ozkar619

Reputation: 11

Have you tried adding the DataContract/DataMember attributes to this ProductId class as well?

i.e:

[DataContract]
public struct ProductId : IEquatable<ProductId>
{

[DataMember]
readonly string productId;

public ProductId(string productId)
{
    this.productId = productId
}

public override string ToString() => productId ?? string.Empty;

}

Also, the name property (Name="ProductId") in this case is not needed, as the variable name is the same as the one you're overriding it with.

Upvotes: 0

Related Questions