让 ServiceStack 保留类型信息

Posted

技术标签:

【中文标题】让 ServiceStack 保留类型信息【英文标题】:Getting ServiceStack to retain type information 【发布时间】:2012-05-31 19:25:20 【问题描述】:

我正在使用 ServiceStack 将一些对象序列化和反序列化为 JSON。考虑这个例子:

public class Container

    public Animal Animal  get; set; 


public class Animal



public class Dog : Animal

    public void Speak()  Console.WriteLine("Woof!"); 


var container = new Container  Animal = new Dog() ;
var json = JsonSerializer.SerializeToString(container);
var container2 = JsonSerializer.DeserializeFromString<Container>(json);

((Dog)container.Animal).Speak(); //Works
((Dog)container2.Animal).Speak(); //InvalidCastException

最后一行抛出 InvalidCastException,因为 Animal 字段被实例化为 Animal 类型,而不是 Dog 类型。有什么方法可以告诉 ServiceStack 保留这个特定实例是 Dog 类型的信息吗?

【问题讨论】:

【参考方案1】:

DTO 中的继承是一个坏主意 - DTO 应该尽可能地自我描述,并且通过使用继承,客户端有效地不知道服务最终返回什么。这就是为什么您的 DTO 类在大多数“基于标准”的序列化程序中无法正确反序列化的原因。

没有充分的理由在 DTO 中使用接口(在 POCO 模型上使用它们的理由也很少),使用接口来减少应用程序代码中的耦合是一种货物崇拜习惯,而这些代码会被轻率地泄露到 DTO 中。但是跨进程边界,接口只会增加耦合(它只是在代码中减少),因为消费者不知道要反序列化的具体类型,所以它必须发出特定于序列化的实现提示,现在将 C# 关注点嵌入到线路上(所以现在甚至C# 命名空间将破坏序列化),现在将您的响应限制为由特定的序列化程序使用。在线路上泄露 C# 问题违反了服务实现互操作性的核心目标之一。

由于 JSON 规范中没有“类型信息”的概念,为了让继承在 JSON 序列化程序中工作,它们需要向JSON wireformat发出专有扩展以包含此类型信息- 现在将您的 JSON 有效负载耦合到特定的 JSON 序列化程序实现。

ServiceStack's JsonSerializer 将此类型信息存储在 __type 属性中,并且由于它会大大膨胀有效负载,因此只会为需要它的类型发出此类型信息,即 Interfaces,后期绑定object 类型或 abstract 类。

话虽如此,解决方案是将Animal 更改为Interfaceabstract 类,但建议不要在DTO 中使用继承。

【讨论】:

如果我有多个服务采用基本相同的 DTO,该怎么办?例如用户注册服务和用户维护服务?两者都期望 UserAccount DTO 与应填写的字段有所不同。存储库期望 UserAccount DTO,因为它将插入或更新 UserAccount 表。所以我创建了一个 UserAccount DTO。 UserRegistration DTO 和 UserMaintenance DTO 继承自它。 [还要在这里非常感谢您创建 ServiceStack。] 呃...回答我自己现在看似显而易见的评论/问题:消息不应该“成为”业务领域对象,它应该“包含”一个(或更多)。 “有”与“是”的简单问题。因此,对于我的示例,UpdateUserMsg 和 RegisterUserMsg 都将包含 UserAccount DTO。两者都不是 UserAccount DTO。无需继承。 你将如何处理没有继承的列表?例如,假设我有一个带有List&lt;IAnimal&gt; 属性的 DTO,然后我有 20 个不同的类实现了IAnimal @Cocowalla IAnimal 具有 20 个具体实现与拥有一个所有 DTO 实现的空 IModel 接口一样有用。这样,您就可以为您的整个应用程序使用 1 个服务,返回 IModel。但这对您的客户有什么影响?没有。它没有提供有关它返回什么的信息,这就是为什么您的服务合同应该是自我描述的,以便消费者知道您的服务返回什么以及他们实现了什么合同。要么使用has vs is 构建它,要么使用不同的服务,要么将它们展平,以便 1 个模型包含您希望它表示的类型的所有属性。 在这个答案中:github.com/ServiceStack/Issues/issues/174 你举了一个例子,比如: JsConfig.IncludeTypeInfo = true; 是否有一种针对所有类型的全局方法,所以我不必写一个包含 3000 多个课程的列表?【参考方案2】:

您只序列化动物对象的属性,无论序列化对象是否为狗。即使您向狗类添加公共属性,例如“Name”,它也不会被序列化,因此当您反序列化时,您只会拥有“Animal”类的属性。

如果你将其更改为以下内容,它将起作用;

public class Container<T> where T: Animal 
        
    public T Animal  get; set;  


public class Animal



public class Dog : Animal

    public void Speak()  Console.WriteLine("Woof!"); 
    public string Name  get; set; 


var c = new Container<Dog>  Animal = new Dog()  Name = "dog1"  ;
var json = JsonSerializer.SerializeToString<Container<Dog>>(c);
var c2 = JsonSerializer.DeserializeFromString<Container<Dog>>(json);

c.Animal.Speak(); //Works
c2.Animal.Speak(); 

【讨论】:

谢谢。不幸的是,我不能使用泛型来解决这个问题——我基本上有一个“List”,每个容器都有不同类型的“Animal”。我希望有一些设置或技巧来解决这个问题,但我想我必须在没有这个功能的情况下生活。【参考方案3】:

也许是题外话,但 Newtonsoft 序列化程序可以做到这一点,包括选项:

            serializer = new JsonSerializer();
        serializer.TypeNameHandling = TypeNameHandling.All;

它将在 json 中创建一个名为 $type 的属性,该属性具有对象的强类型。当您调用反序列化器时,该值将用于再次构建具有相同类型的对象。下一个测试使用强类型的 newtonsoft,而不是使用 ServiceStack

 [TestFixture]
public class ServiceStackTests

    [TestCase]
    public void Foo()
    
        FakeB b = new FakeB();
        b.Property1 = "1";
        b.Property2 = "2";

        string raw = b.ToJson();
        FakeA a=raw.FromJson<FakeA>();
        Assert.IsNotNull(a);
        Assert.AreEqual(a.GetType(), typeof(FakeB));
    


public abstract class FakeA

    public string Property1  get; set; 


public class FakeB:FakeA

    public string Property2  get; set; 

【讨论】:

以上是关于让 ServiceStack 保留类型信息的主要内容,如果未能解决你的问题,请参考以下文章

让 servicestack 在 monodroid 上工作

使用 JSON 时如何让 ServiceStack 用破折号格式化 Guid?

解析 ServiceStack 服务并定义内容类型

如何使用 servicestack 类型的客户端?

ServiceStack JSON 类型定义应以“”开头

ServiceStack.Text JsonSerializer 无法反序列化其自己的序列化模式(类型定义应以 '' SerializationException 开头)