2013-10-04 3 views
1

J'ai un point de terminaison POST qui prend un paramètre de chemin d'URL et le corps est une liste de DTO soumis.ServiceStack: Puis-je "Aplatir" la structure du corps du message?

Donc maintenant la demande DTO ressemble à quelque chose le long des lignes de:

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

Et la méthode de service ressemble à quelque chose comme:

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

Si codé via JSON, le client devrait encoder le corps POST de cette façon:

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

Ceci est redondant, je voudrais que le client soumette les données comme ceci:

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

Ce qui aurait été le cas si ma demande DTO était simplement List<SomeEntry>

Mes questions est: est-il un moyen de « aplatir » la demande de cette façon? Ou désigner une propriété de la demande en tant que racine du corps du message? c'est-à-dire peut-être:

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

Est-ce faisable de quelque manière que ce soit dans ServiceStack?

Répondre

0

OK J'ai réussi à atteindre cet objectif. Pas la plus jolie solution mais fera pour l'instant.

J'enveloppa le filtre de type de contenu pour JSON:

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

Ensuite désérialiseur personnalisé ressemble à ceci:

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 est peuplé après initialisation en balayant les DTO de demande et la recherche d'un certain attribut, comme dans l'exemple dans ma question initiale.

1

j'ai pu obtenir sorte de cela genre de travail par le sous-classement List<T>:

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

vous pouvez envoyer une demande comme celui-ci:

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

Mais si vous avez un choix le design, je ne le recommanderais pas. Voici quelques raisons pour commencer:

  • J'ai trouvé au moins un problème avec ceci lors de l'exécution: envoyer un tableau vide, par ex. [ ] dans JSON, aboutit à un code d'état 400 avec RequestBindingException
  • C'est moins flexible. Que faire si vous devez ajouter des propriétés de niveau supérieur à la demande dans le futur? Vous seriez coincé avec eux étant des paramètres de chemin/requête. Avoir une liste-contenant-une-liste régulière vous permet d'ajouter de nouvelles propriétés facultatives au niveau supérieur du corps de la requête, avec compatibilité ascendante
+1

Remarque: Si vous héritez d'une collection, vous ne devez ajouter aucune propriété car elle ne se comporte pas comme prévu (JSON ne prend en charge que les tableaux ou les littéraux, mais pas les deux).La solution consiste pour les DTO à refléter la même forme que le format de fil, ce qui est essentiellement le but des DTO - pour représenter le format de câble, allégeant l'effort dans les réponses de/sérialisation. C'est une proposition perdue pour essayer d'injecter un comportement magique pendant la sérialisation, ce qui est peu connu des clients, quand la traduction vers différents DTO avec les formes désirées en C# est plus simple, plus flexible et plus facile à raisonner. – mythz

+0

Hi mythz, Ce que j'essaie de réaliser est parfaitement faisable en Java avec Jersey ou Spring MVC, ou dans Rails. Les clients C# ne sont pas les seuls clients. Pour tout autre client autre que le client ServiceStack, ce niveau supplémentaire redondant n'aurait pas de sens. Imaginez par exemple que mon service ci-dessus est consommé à partir d'un front-end de rails, ce qui est le cas. Ce comportement n'est pas magique, il est très bien défini - puisque le DTO représente la requête entière, répartie sur le chemin, les variables de chaîne de requête et le corps, il s'ensuit qu'une seule propriété peut représenter tout le corps. C'est un cas d'utilisation courant. –

+0

La ligne de fond est, comme vous le dites, le but de la DTO est de représenter la demande. Vous avez également déclaré que vous voulez que ServiceStack soit un logiciel sans opinion (une approche avec laquelle je suis tout à fait d'accord). Eh bien, il y a une certaine forme valide de la façon dont je veux concevoir mon interface REST, ce qui n'est pas réalisable avec une requête DTO dans ServiceStack. –

Questions connexes