【问题标题】:DataContractSerializer for custom Value types用于自定义值类型的 DataContractSerializer
【发布时间】:2018-05-03 09:00:00
【问题描述】:

我有一个 WCF 服务,它公开了一个类型 TestTypeOne,该类型当前有一个名为 ProductId 的字符串属性:

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

我想将此属性的类型从 string 更改为名为 ProductId 的自定义值类型,但不会破坏 WCF 合同(这仅适用于服务器端,客户端应将 ProductId 视为仍然是一个字符串。)

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

自定义类型是这样的(为简洁起见,大部分代码被删除):

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

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

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

使用以下测试代码:

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());

序列化时的预期输出应该是:

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

我尝试过实现ISerializable,但这似乎只能让我控制ProductId xml 标签的内容,而不是标签本身(所以我可以让&lt;ProductId&gt;&lt;something&gt;1234113&lt;/something&gt;&lt;/ProductId&gt; 之类的事情发生)。

理想情况下,我可以对 ProductId 类型本身做一些事情,因为这种类型在许多地方和许多合同中都有使用。

【问题讨论】:

  • 你也需要反序列化这个类型吗?
  • @Evk 是的,但仍然只在服务器端(因此客户端应该以字符串属性结束,只有服务器在与 ProductId 进行转换)

标签: c# wcf datacontractserializer


【解决方案1】:

我认为最简单的方法是实现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);
    }
}

要针对这种情况调整 WCF xsd 生成(强制它生成 xs:string) - 您可以使用数据协定代理来生成 xsd。例如你可以有这样的 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();
    }
}

其唯一目的是说ProductId 类型的数据协定实际上是string

然后你可以使用这个代理来生成模式:

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

您可以将这种方法用于序列化本身,但我发现它更复杂。

您可以阅读有关代理和 WCF here 的更多信息,最底部有一个示例,说明如何将代理用于 WSDL 生成端点(“将代理用于元数据导出”部分)。

【讨论】:

【解决方案2】:

您是否尝试将 DataContract/DataMember 属性也添加到此 ProductId 类?

即:

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

[DataMember]
readonly string productId;

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

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

}

此外,在这种情况下不需要 name 属性 (Name="ProductId"),因为变量名称与您覆盖它的名称相同。

【讨论】:

  • 是的,不幸的是,这会在 xml 中生成:&lt;ProductId&gt;&lt;productId&gt;1234567890123&lt;/productId&gt;&lt;/ProductId&gt;,而不是想要的 &lt;ProductId&gt;1234567890123&lt;/ProductId&gt;
猜你喜欢
  • 1970-01-01
  • 2010-12-30
  • 1970-01-01
  • 1970-01-01
  • 2017-06-18
  • 2011-03-10
  • 2011-12-18
  • 2020-10-17
相关资源
最近更新 更多