【发布时间】:2018-02-20 07:40:24
【问题描述】:
我正在使用 ServiceStack 使用第三方 WebApi。想象一下这个 API 有以下路线。
https://api.example.com/v1/people/{id} 返回具有指定 ID 的人。
JSON:
{
"id": 1,
"name": "Jean-Luc Picard"
}
我可以使用以下 C# 代码来使用它。
class Program
{
static void Main(string[] args)
{
var client = new JsonServiceClient("https://api.example.com/v1/");
Person person = client.Get(new GetPerson() { ID = 1 });
}
}
[Route("/people/{id}")]
public class GetPerson : IReturn<Person>
{
public int ID { get; set; }
}
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
}
我想改用explicit response DTO,以防 API 发生变化。问题是 /people/{id} 端点返回的 JSON 是一个裸的 Person 对象。假设 v2 中的响应发生了变化。
JSON:
{
"person": {
"id": 1,
"name": "Jean-Luc Picard"
}
}
有了这个响应,下面的代码就可以工作了。
[Route("/people/{id}")]
public class GetPerson : IReturn<GetPersonResponse>
{
public int ID { get; set; }
}
public class GetPersonResponse
{
public Person Person { get; set; }
}
我想将这个GetPersonResponse 及其Person 属性用于上面不封装人员数据的当前JSON。我知道我们可以使用System.Runtime.Serialization 中的DataContract 和DataMember 属性来控制JSON 元素如何映射到DTO,但我认为没有任何方法可以映射根元素。有什么方法可以提示ServiceStack.Text JSON Deserializer 当前 JSON 的根元素需要反序列化为这个 Person 对象?
我确定的最佳解决方案是使用属性来解决这个问题。
public class GetPersonResponse
{
private int _id;
private string _name;
private Person _person;
public int ID { get => _id; set { _id = value; if(_person != null) _person.ID = _id; } }
public string Name { get => _name; set { _name = value; if (_person != null) _person.Name = _name; } }
public Person Person { get => _person ?? new Person() { ID = this.ID, Name = this.Name }; set => _person = value; }
}
这是站不住脚的,因为它没有正确封装。 ID 和名称必须保持公开,以便 JSON 反序列化程序能够访问它们。真正的 DTO 也有 30 多个字段,所以这将是一场噩梦。
这样做的目的是使应用程序与客户端库分离,并且只需更新客户端库即可利用新的 API 版本。应用程序将继续访问response.Person,就好像什么都没发生一样。
最终,这归结为ServiceStack.Text 问题。是否可以编写GetPersonResponse1 的版本,除了Person 之外没有公共属性并且没有样板代码,这样以下断言就可以通过了吗?
using ServiceStack;
using System;
using System.Diagnostics;
class Program
{
static void Main(string[] args)
{
string v1 = "{\"id\":1,\"name\":\"Jean-Luc Picard\"}";
string v2 = "{\"person\":{\"id\":1,\"name\":\"Jean-Luc Picard\"}}";
GetPersonResponse1 p1 = v1.FromJson<GetPersonResponse1>();
GetPersonResponse2 p2 = v2.FromJson<GetPersonResponse2>();
Debug.Assert(p1.Person != null
&& p2.Person != null
&& p1.Person.ID == p2.Person.ID
&& p1.Person.Name == p2.Person.Name);
}
}
public class GetPersonResponse1
{
public Person Person { get; set; }
}
public class GetPersonResponse2
{
public Person Person { get; set; }
}
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
}
更新
电线上的数据形状变化是一个问题,因为我不控制电线 - 它是第 3 方 WebAPI。 WebAPI 实现了 HATEOAS HAL 超媒体类型,因此响应中包含不属于模型的 _links 和其他数据。对于某些端点,它当前返回裸对象。可以想象,如果他们将非模型元数据添加到响应中,那么模型数据将被移动到响应中的 _embedded 元素中。
Application (3rd Party)
=> Client Library (Me)
=> WebAPI (3rd Party)
我的错误是想象应用程序将直接与响应 DTO 一起工作。回想起来,由于多种原因,这没有意义。相反,响应 DTO 应该明确地遵循线路上数据的形状(神话的第一个建议)。然后客户端库应该公开一个抽象层供应用程序与之交互(神话的第二个建议)。
Application
=> Client Library API
=> Client Library Response DTOs
=> WebAPI
我确实计划看看 RestSharp,看看它是否更适合我的用例,但决定先从 ServiceStack 开始。
【问题讨论】:
标签: servicestack servicestack-text