1

I have a POST endpoint that takes a URL path param and then the body is a list of submitted DTOs.

So right now the request DTO looks something along the lines of:

[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; }
}

And the service method looks something like:

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

If encoded via JSON, the client would have to encode the POST body this way:

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

This is redundant, I would like the client to submit the data like this:

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

Which would have been the case if my request DTO was simply List<SomeEntry>

My questions is: is there a way to "flatten" the request this way? Or designate one property of the request as the root of the message body? i.e perhaps:

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

Is this doable in any way in ServiceStack?

Amir Abiri
  • 8,847
  • 11
  • 41
  • 57

2 Answers2

1

I was able to sort of get this to sort of work by subclassing List<T>:

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

Then you can send a request like this:

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

But if you have any choice in the design, I wouldn't recommend this. Here's a couple of reasons to start with:

  • I found at least one problem with this at runtime: sending an empty array, e.g. [ ] in JSON, is resulting in a 400 status code with RequestBindingException
  • It's less flexible. What if you do need to add additional top-level properties to the request in the future? You would be stuck with them being path/query params. Having a regular class-containing-a-list allows you to add new optional properties at the top level of the request body, with backward compatibility
Mike Mertsock
  • 11,825
  • 7
  • 42
  • 75
  • 1
    Note: If you're inheriting from a Collection, you shouldn't add any properties as it will not behave as expected (JSON only supports Arrays or Literals, not both). The solution is for DTOs to reflect the same shape as the wire-format, which is essentially the purpose of DTO's - to represent the wire-format, alleviating the effort in de/serializing responses. It's a losing proposition to try inject magic behavior during serialization, which is unlikely to be known to clients, when translating to different DTO's with the desired shapes in C# are simpler, more flexible and easier to reason about. – mythz Oct 04 '13 at 17:20
  • Hi mythz, What I'm trying to achieve is perfectly doable in Java with Jersey or Spring MVC, or in Rails. C# clients aren't the only clients. For any other client other than the ServiceStack client, this redundant extra level wouldn't make sense. Imagine for example that my service above is consumed from a rails front-end, which is the case. This isn't magic behaviour, it's very well defined - since the DTO represents the whole request, spread over the path, query string variables and the body, it only follows that one property could represent the whole body. It's a common use case. – Amir Abiri Oct 04 '13 at 17:54
  • The bottom line is, as you say - the purpose of the DTO is to represent the request. You've also stated that you aim ServiceStack to be non-opinionated software (an approach I very much agree with). Well here there is a certain valid form of how I want to design my REST interface, which isn't achievable with a request DTO in ServiceStack. – Amir Abiri Oct 04 '13 at 17:56
  • @esker: That's a clever way to achieve this which I didn't think about. But I think I'll go with my solution for all the reasons mentioned. – Amir Abiri Oct 04 '13 at 17:58
  • @AmirAbiri Best to stick with your Java solution, ServiceStack is designed to promote 'pit of success' development of web services - it is opinionated towards this. Refer to [definition of DTO's](http://martinfowler.com/eaaCatalog/dataTransferObject.html) and [Request DTOs/ServiceLayer is important](http://stackoverflow.com/a/15369736/85785). This means we promote using DTO's as they were intended, i.e. as representations of value objects that controls de/serialization over the wire. Not being able to represent the desired wire-format with a programatic object model, is a code-smell to avoid. – mythz Oct 04 '13 at 18:03
  • @mythz Wow, didn't mean to start holy war - sorry if you got offended. I've moved away from Java and I work with C# now and I looked for the equivalents of Jersey/Spring MVC in C#. ServiceStack is a very powerful framework and I chose it over WCF in an instant. I only mentioned Jersy/Spring MVC because I thought it's worth looking at what others are doing to get ideas. – Amir Abiri Oct 04 '13 at 18:13
  • I know what DTOs are, but the question is what you conceptualize the DTO to represent. You could argue that in the case of a request with a message body you are sending two DTOs - the path/query string meta-data and the message body itself. – Amir Abiri Oct 04 '13 at 18:13
  • They should be for transferring [value objects](http://www.infoq.com/presentations/Value-Values) over the wire not coupled to server logic distorting their natural representation. Generating a wire-format that doesn't fit into a clean object model causes issues for service consumers and makes it harder to infer the wire-format based on the DTO Service Contracts (i.e. master schema) - which is why they're not recommended. – mythz Oct 04 '13 at 18:27
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/38634/discussion-between-amir-abiri-and-mythz) – Amir Abiri Oct 04 '13 at 18:31
  • Actually meant to link to Rich Hickey's other presentation: [The language of the system](http://www.youtube.com/watch?v=ROor6_NGIWU). Although there's great value in [watching them all](http://thechangelog.com/rich-hickeys-greatest-hits/). – mythz Oct 04 '13 at 18:41
  • I think this belongs in the chat from here on out. I would appreciate if you did join because I'm not sure I managed to get my meaning across yet. But that's up to you of course. – Amir Abiri Oct 04 '13 at 22:38
0

OK I've managed to achieve this. Not the prettiest solution but will do for now.

I wrapped the content type filter for JSON:

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

Then the custom deserializer looks like this:

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 is populated after init by scanning the request DTOs and looking for a certain attribute, as in the example in my original question.

Amir Abiri
  • 8,847
  • 11
  • 41
  • 57