web-dev-qa-db-fra.com

Comment puis-je retourner json à partir de mon service de repos WCF (.NET 4), en utilisant Json.Net, sans que ce soit une chaîne, entre guillemets?

MISE À JOUR 10/19/2010 Je sais que j'ai posé cette question il y a quelque temps, mais les solutions de contournement présentées dans ces réponses ne sont guère satisfaisantes, et c'est toujours un problème commun pour beaucoup. WCF n'est tout simplement pas flexible. J'ai commencé ma propre bibliothèque open source C # pour créer des services REST sans WCF. Vérifiez restcake.net ou rest.codeplex.com pour plus d'informations sur ladite bibliothèque. FIN DE LA MISE À JOUR

MISE À JOUR 8/2/2012 API Web ASP.NET (auparavant API Web WCF, le remplacement de REST WCF) utilise Json.NET par défaut FIN DE MISE À JOUR

DataContractJsonSerializer est incapable de gérer de nombreux scénarios que Json.Net gère très bien lorsqu'il est correctement configuré (en particulier, les cycles).

Une méthode de service peut renvoyer un type d'objet spécifique (dans ce cas, un DTO ), auquel cas le DataContractJsonSerializer sera utilisé, ou Je peux demander à la méthode de renvoyer une chaîne et d'effectuer moi-même la sérialisation avec Json.Net. Le problème est que lorsque je retourne une chaîne json par opposition à un objet, le json envoyé au client est entouré de guillemets.

En utilisant DataContractJsonSerializer, renvoyant un type d'objet spécifique, la réponse est:
{"Message":"Hello World"}

En utilisant Json.Net pour renvoyer une chaîne json, la réponse est:
"{\"Message\":\"Hello World\"}"

Je ne veux pas avoir à eval () ou JSON.parse () le résultat sur le client, ce que je devrais faire si le json revient sous forme de chaîne, entouré de guillemets. Je me rends compte que le comportement est correct; ce n'est tout simplement pas ce que je veux/besoin. J'ai besoin du json brut; le comportement lorsque le type de retour de la méthode de service est un objet, pas une chaîne.

Alors, comment puis-je demander à ma méthode de renvoyer un type d'objet, mais pas utiliser DataContractJsonSerializer? Comment puis-je lui dire d'utiliser le sérialiseur Json.Net à la place?

Ou existe-t-il un moyen d'écrire directement dans le flux de réponse? Donc je peux juste retourner le json brut moi-même? Sans les guillemets enveloppants?

Voici mon exemple artificiel, pour référence:

[DataContract]
public class SimpleMessage
{
    [DataMember]
    public string Message { get; set; }
}

[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class PersonService
{
    // uses DataContractJsonSerializer
    // returns {"Message":"Hello World"}
    [WebGet(UriTemplate = "helloObject")]
    public SimpleMessage SayHelloObject()
    {
        return new SimpleMessage("Hello World");
    }

    // uses Json.Net serialization, to return a json string
    // returns "{\"Message\":\"Hello World\"}"
    [WebGet(UriTemplate = "helloString")]
    public string SayHelloString()
    {
        SimpleMessage message = new SimpleMessage() { Message = "Hello World" };
        string json = JsonConvert.Serialize(message);
        return json;
    }

    // I need a mix of the two.  Return an object type, but use the Json.Net serializer.
}
40
Samuel Meacham

J'ai finalement trouvé une solution à cela. Ce n'est pas ce que j'aurais préféré (qui serait de renvoyer le type d'objet spécifique et de demander en quelque sorte à WCF d'utiliser un sérialiseur Json.Net, au lieu du DataContractJsonSerializer), mais cela fonctionne très bien, et c'est simple et clair.

Étendre mon exemple artificiel en utilisant cette nouvelle solution:

[WebGet(UriTemplate = "hello")]
public void SayHello()
{
    SimpleMessage message = new SimpleMessage() {Message = "Hello World"};
    string json = JsonConvert.Serialize(message);
    HttpContext.Current.Response.ContentType = "application/json; charset=utf-8";
    HttpContext.Current.Response.Write(json);
}

Notez le type de retour de void. Nous ne retournons rien, car il serait sérialisé avec DataContractJsonSerializer. Au lieu de cela, j'écris directement dans le flux de sortie de réponse. Étant donné que le type de retour est vide, le pipeline de traitement ne définit pas le type de contenu sur le type par défaut "application/json", donc je le définis explicitement.

Parce que cela utilise HttpContext, je suppose que cela ne fonctionnera que si vous avez [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)] sur votre classe de service, car cela forcera les demandes au service à passer par le pipeline ASP.NET. Sans la compatibilité asp.net, le HttpContext ne sera pas disponible, car l'hébergement wcf est censé être indépendant de l'hôte.

En utilisant cette méthode, les résultats semblent parfaits dans Firebug pour les requêtes GET. Type de contenu correct, longueur de contenu correcte et json brut, non encapsulés entre guillemets. Et, j'obtiens la sérialisation que je veux en utilisant Json.Net. Le meilleur des deux mondes.

Je ne suis pas sûr à 100% des obstacles que je pourrais rencontrer en ce qui concerne de la sérialisation, lorsque mes méthodes de service ont des types d'objet [DataContract] comme paramètres d'entrée. Je suppose que le DataContractJsonSerializer sera également utilisé pour cela. Traversera ce pont quand j'y arriverai ... si cela crée un problème. Ce n'est pas encore le cas, avec mes simples DTO.

UPDATE Voir la réponse d'Oleg (la partie UPDATE2). Il change le type de retour de la méthode de service de void en System.ServiceModel.Channels.Message, Et plutôt que d'utiliser HttpContext.Current.Response.Write(), il utilise:

return WebOperationContext.Current.CreateTextResponse (json,
    "application/json; charset=utf-8", Encoding.UTF8);

Ce qui est en effet une meilleure solution. Merci Oleg.

PDATE 2 Il y a encore une autre façon d'accomplir cela. Changez le type de retour de votre service de Message en Stream et retournez ceci:

WebOperationContext.Current.OutgoingResponse.ContentType = "application/json; charset=utf-8";
return new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json));

Je n'ai pas fait de tests spécifiques, mais il est possible que ce soit un meilleur choix pour les méthodes qui pourraient potentiellement renvoyer de grandes quantités de données. Je ne sais pas si cela compte pour les données non binaires. Quoi qu'il en soit, une pensée.

40
Samuel Meacham

Il me semble que vous n'utilisez pas correctement DataContractJsonSerializer. Ce qui est étrange, c'est que vous ne définissez pas l'attribut ResponseFormat = ResponseFormat.Json Pour la méthode public SimpleMessage SayHelloObject().

De plus, si vous avez {"Message":"Hello World"} Dans une chaîne et que vous l'affichez dans le débogueur, il sera affiché comme "{\"Message\":\"Hello World\"}", Donc exactement comme vous voyez string json = JsonConvert.Serialize(message); (Json.Net). Il me semble donc que vous avez dans les deux cas les mêmes résultats.

Pour vérifier cela, utilisez un logiciel client qui lit les résultats. Voir quelques exemples

L'appel ajQuery JQuery à la méthode web httpget (c #) ne fonctionne pas

Puis-je retourner JSON à partir d'un service Web .asmx si le ContentType n'est pas JSON?

Comment créer un objet JSON à envoyer à un AJAX WebService?

[~ # ~] mis à jour [~ # ~] : Dans votre code, vous définissez la méthode SayHelloString(). Son résultat est une chaîne. Si vous appelez la méthode, cette chaîne sera une fois de plus JSON sérialisée. La sérialisation JSON de la chaîne {"Message":"Hello World"} Est une chaîne entre guillemets (voir http://www.json.org/ définition non pas un objet, mais une chaîne) ou exactement chaîne "{\"Message\":\"Hello World\"}". Tout est donc correct avec les deux méthodes de votre service Web.

MISE À JOUR 2 : Je suis heureux que mon conseil de la partie "Mise à jour" de ma réponse vous ait aidé à changer la double sérialisation JSON.

Néanmoins je vous recommanderais de changer un peu la solution pour rester plus au concept WCF.

Si vous souhaitez implémenter un encodage personnalisé de la réponse Web dans WCF (voir http://msdn.Microsoft.com/en-us/library/ms734675.aspx ) votre méthode WCF devrait mieux renvoyer Message au lieu de void:

[WebGet(UriTemplate = "hello")]
public Message SayHello()
{
    SimpleMessage message = new SimpleMessage() {Message = "Hello World"};
    string myResponseBody = JsonConvert.Serialize(message);
    return WebOperationContext.Current.CreateTextResponse (myResponseBody,
                "application/json; charset=utf-8",
                Encoding.UTF8);
}

Vous pouvez en effet utiliser un autre formateur de Message: par exemple CreateStreamResponse (ou un autre voir http://msdn.Microsoft.com/en-us/library/system.servicemodel.web.weboperationcontext_methods ( v = VS.100) .aspx ) au lieu de CreateTextResponse. Si vous souhaitez définir des en-têtes HTTP supplémentaires ou un code d'état Http (par exemple en cas d'erreur), vous pouvez le faire de cette façon:

OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
ctx.StatusCode = HttpStatusCode.BadRequest;

À la fin, je veux répéter ma question à partir d'un commentaire: pourriez-vous expliquer pourquoi vous voulez utiliser Json.Net Au lieu de DataContractJsonSerializer? S'agit-il d'une amélioration des performances? Avez-vous besoin d'implémenter la sérialisation de certains types de données comme DateTime d'une autre manière que DataContractJsonSerializer? Ou la raison principale de votre choix de Json.Net En est une autre?

11
Oleg