我正在使用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。我知道我们可以使用DataContract
中的DataMember
和System.Runtime.Serialization
属性来控制JSON元素如何映射到DTO,但我认为没有任何方法可以映射根元件。有没有办法提示ServiceStack.Text
JSON反序列化器需要将当前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; }
}
电线更换数据的形状是一个问题,因为我不控制电线 - 它是第三方WebAPI。 WebAPI实现了HATEOAS HAL超媒体类型,因此响应中的_links和其他数据不属于模型。对于某些端点,它当前返回裸对象。可以想象,如果他们将非模型元数据添加到响应中,那么模型数据将被移动到响应中的_embedded元素。
Application (3rd Party)
=> Client Library (Me)
=> WebAPI (3rd Party)
我的错误是想象应用程序可以直接使用响应DTO。回想起来,由于一些原因,这没有意义。相反,响应DTO应该明确地遵循线上数据的形状(mythz'第一推荐)。然后客户端库应该公开一个抽象层,供应用程序与之交互(mythz'第二推荐)。
Application
=> Client Library API
=> Client Library Response DTOs
=> WebAPI
我打算看看RestSharp,看看它是否更适合我的用例,但决定先从ServiceStack开始。
答案 0 :(得分:2)
purpose of DTOs(数据传输对象)用于定义具有与有线格式形状匹配的类型模式的服务合同,以便它们可以与通用序列化程序一起使用,以自动序列化/反序列化有效负载而无需手动定制逻辑样板。
所以我不理解为什么你试图隐藏DTO的公共模式以及为什么类型包含嵌入式逻辑,这两者都是DTO的反模式,它们应该是良性的无内容数据结构。
我的第一个建议是更改您的类型,使它们成为DTO,其公共架构与其尝试反序列化的数据的形状相匹配。
如果没有这个,假设你的类型不是DTO而你想用它来水化数据模型我会考虑创建一个单独的Typed DTO并使用AutoMapping library(或自定义Type mapper扩展方法) )将类型DTO中的数据复制到理想的数据模型中。
如果您不想在数据模型中维护单独的Typed Data DTO,可以使用generic JSON parser将任意数据结构反序列化为松散类型的通用.NET数据结构,如{{1} }。
如果您只是想避开公共属性并且很乐意拥有公共字段,您可以指定ServiceStack.Text的序列化程序来填充公共字段:
Dictionary<string,object>