【问题标题】:ServiceStack: Can I "Flatten" the structure of the post body?ServiceStack:我可以“扁平化”帖子正文的结构吗?
【发布时间】:2013-10-04 05:01:12
【问题描述】:

我有一个 POST 端点,它采用 URL 路径参数,然后正文是提交的 DTO 列表。

所以现在请求 DTO 看起来类似于:

[Route("/prefix/{Param1}", "POST")]
public class SomeRequest
{
    public string          Param1  { get; set; }
    public List<SomeEntry> Entries { get; set; }
}

public class SomeEntry
{
    public int    ID    { get; set; }
    public int    Type  { get; set; }
    public string Value { get; set; }
}

服务方法看起来像:

public class SomeService : Service
{
    public SomeResponse Post(SomeRequest request)
    {
    }
}

如果通过 JSON 编码,客户端必须以这种方式编码 POST 正文:

{
    "Entries":
    [
        {
            "id":    1
            "type":  42
            "value": "Y"
        },
        ...
    ]
}

这是多余的,我希望客户端这样提交数据:

[
    {
        "id":    1
        "type":  42
        "value": "Y"
    },
    ...
]

如果我的请求 DTO 只是 List&lt;SomeEntry&gt; 会是这种情况

我的问题是:有没有办法以这种方式“扁平化”请求?或者将请求的一个属性指定为消息体的根?即也许:

[Route("/prefix/{Param1}", "POST")]
public class SomeRequest
{
    public string          Param1  { get; set; }
    [MessageBody]
    public List<SomeEntry> Entries { get; set; }
}

这在 ServiceStack 中是否可行?

【问题讨论】:

    标签: c# rest servicestack


    【解决方案1】:

    我可以通过继承List&lt;T&gt; 来完成这项工作:

    [Route("/prefix/{Param1}", "POST")]
    public class SomeRequest : List<SomeEntry>
    {
        public string          Param1  { get; set; }
    }
    

    然后你可以这样发送请求:

    POST /prefix/someParameterValue
    Content-Type: application/json
    [ { "ID": 1, "Type": 2, "Value": "X" }, ... ]
    

    但如果您对设计有任何选择,我不建议您这样做。以下是几个开始的理由:

    • 我在运行时发现了至少一个问题:发送一个空数组,例如[ ] 在 JSON 中,导致400 状态代码为RequestBindingException
    • 它不太灵活。如果您将来确实需要向请求中添加额外的顶级属性怎么办?您会被它们作为路径/查询参数卡住。拥有一个包含常规类的列表允许您在请求正文的顶层添加新的可选属性,并具有向后兼容性

    【讨论】:

    • 注意:如果您从集合继承,则不应添加任何属性,因为它不会按预期运行(JSON 仅支持数组或文字,而不支持两者)。解决方案是让 DTO 反映与线路格式相同的形状,这本质上是 DTO 的目的 - 表示线路格式,减轻反序列化响应的工作量。尝试在序列化过程中注入魔术行为是一个失败的提议,当在 C# 中转换为具有所需形状的不同 DTO 时,客户端不太可能知道这种行为更简单、更灵活且更容易推理。
    • 嗨,mythz,我想要实现的目标是在 Java 中使用 Jersey 或 Spring MVC 或在 Rails 中完全可行。 C# 客户端并不是唯一的客户端。对于除 ServiceStack 客户端之外的任何其他客户端,这种多余的额外级别没有意义。例如,想象一下我上面的服务是从 Rails 前端消耗的,就是这种情况。这不是魔术行为,它的定义非常明确——因为 DTO 代表整个请求,分布在路径、查询字符串变量和正文中,它只遵循一个属性可以代表整个正文。这是一个常见的用例。
    • 正如您所说,底线是 - DTO 的目的是表示请求。您还说过,您的目标是将 ServiceStack 定位为无主见的软件(我非常同意这种方法)。好吧,这里有一种我想如何设计我的 REST 接口的有效形式,这是通过 ServiceStack 中的请求 DTO 无法实现的。
    • @esker:这是实现这一目标的聪明方法,我没有想到。但我认为出于上述所有原因,我会采用我的解决方案。
    • @AmirAbiri 最好坚持使用您的 Java 解决方案,ServiceStack 旨在促进 Web 服务的“成功坑”开发 - 它对此固执己见。请参阅definition of DTO'sRequest DTOs/ServiceLayer is important。这意味着我们提倡按预期使用 DTO,即作为控制线路上的反序列化的值对象的表示。无法使用编程对象模型表示所需的线格式,这是一种需要避免的代码气味。
    【解决方案2】:

    好的,我已经成功实现了这一点。不是最漂亮的解决方案,但现在可以。

    我为 JSON 包装了内容类型过滤器:

    var serz   = ContentTypeFilters.GetResponseSerializer("application/json");
    var deserz = ContentTypeFilters.GetStreamDeserializer("application/json");
    ContentTypeFilters.Register("application/json", serz, (type, stream) => MessageBodyPropertyFilter(type, stream, deserz));
    

    那么自定义的反序列化器是这样的:

    private object MessageBodyPropertyFilter(Type type, Stream stream, StreamDeserializerDelegate original)
    {
        PropertyInfo prop;
        if (_messageBodyPropertyMap.TryGetValue(type, out prop))
        {
            var requestDto = type.CreateInstance();
            prop.SetValue(requestDto, original(prop.PropertyType, stream), null);
            return requestDto;
        }
        else
        {
            return original(type, stream);
        }
    }
    

    _messageBodyPropertyMap 在初始化后通过扫描请求 DTO 并查找某个属性来填充,如我原始问题中的示例所示。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-15
      • 1970-01-01
      • 2015-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-17
      • 2017-04-22
      相关资源
      最近更新 更多