Как я могу вернуть json из моего сервиса WCF rest (.NET 4), используя Json.Net, без его строки, заключенной в кавычки?
ОБНОВЛЕНИЕ 10/19/2010
Я знаю, что я задал этот вопрос некоторое время назад, но обходные пути, показанные в этих ответах, вряд ли удовлетворительны, и это по-прежнему является общей проблемой для многих. WCF просто не является гибким. Я начал свою собственную библиотеку С# с открытым исходным кодом для создания служб REST без WCF. Проверьте restcake.net или rest.codeplex.com для получения информации об этом библиотека.
END UPDATE
ОБНОВЛЕНИЕ 8/2/2012
ASP.NET Web API (ранее WCF Web API, замена REST WCF) использует Json.NET по умолчанию
END UPDATE
DataContractJsonSerializer
не может обрабатывать многие сценарии, в которых Json.Net обрабатывает только штраф при правильной настройке (в частности, циклов).
Метод службы может либо возвращать определенный тип объекта (в этом случае DTO), и в этом случае используется DataContractJsonSerializer
, или я могу заставить метод вернуть строку, а сама сериализация с Json.Net. Проблема в том, что когда я возвращаю json-строку в отличие от объекта, json, который отправляется клиенту, заключен в кавычки.
Используя DataContractJsonSerializer
, возвращая определенный тип объекта, ответ будет следующим:
{"Message":"Hello World"}
Используя Json.Net для возврата строки json, ответ будет следующим:
"{\"Message\":\"Hello World\"}"
Я не хочу иметь для eval() или JSON.parse() результат на клиенте, и это то, что мне нужно было бы сделать, если json вернется как строка, завернутая в кавычки. Я понимаю, что поведение правильное; это просто не то, что я хочу/нуждаюсь. Мне нужен сырой json; поведение, когда возвращаемый тип метода службы является объектом, а не строкой.
Итак, как я могу вернуть метод методу типа объекта, но не использовать DataContractJsonSerializer? Как я могу сказать, чтобы вместо этого использовать сериализатор Json.Net?
Или, может ли кто-нибудь непосредственно написать ответный поток? Значит, я могу просто вернуть сырого json? Без кавычек обертывания?
Вот мой надуманный пример, для справки:
[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.
}
Ответы
Ответ 1
Наконец-то я понял, как это решить. Это не то, что я бы предпочел (что бы было вернуть конкретный тип объекта и каким-то образом поручить WCF использовать сериализатор Json.Net вместо DataContractJsonSerializer), но он отлично работает, и он прост и понятен.
Расширение моего надуманного примера с помощью этого нового решения:
[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);
}
Обратите внимание на тип возврата void
. Мы ничего не возвращаем, поскольку он будет сериализован с помощью DataContractJsonSerializer. Вместо этого я пишу непосредственно в выходной поток ответа. Поскольку тип возврата недействителен, конвейер обработки не устанавливает тип содержимого в тип по умолчанию "application/json", поэтому я устанавливаю его явно.
Поскольку это использует HttpContext
, я предполагаю, что он будет работать, только если у вас есть [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
в вашем классе службы, поскольку это заставит запросы службы проходить через конвейер ASP.NET. Без совместимости с asp.net HttpContext будет недоступен, поскольку wcf-хостинг считается агностиком хоста.
Используя этот метод, результаты выглядят идеально в firebug для запросов GET. Правильный контент-тип, правильная длина контента и raw json, а не завернутые в кавычки. И я получаю сериализацию, которую я хочу использовать Json.Net. Лучшее из обоих миров.
Я не на 100% уверен, какие препятствия могут возникнуть в отношении десериализации, когда мои методы обслуживания имеют типы объектов DataContract в качестве входных параметров. Я предполагаю, что DataContractJsonSerializer тоже будет использован для этого. Перейду через этот мост, когда я приду к нему... если это создаст проблему. Пока это не так, с моими простыми DTO.
UPDATE
См. Ответ Олега (часть UPDATE2). Он изменяет тип возврата метода службы с void на System.ServiceModel.Channels.Message
, и вместо использования HttpContext.Current.Response.Write()
он использует:
return WebOperationContext.Current.CreateTextResponse (json,
"application/json; charset=utf-8", Encoding.UTF8);
Это действительно лучшее решение. Спасибо, Олег.
ОБНОВЛЕНИЕ 2
Есть еще один способ добиться этого. Измените тип возвращаемого сервиса из сообщения в поток и верните его:
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json; charset=utf-8";
return new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json));
Я не проводил никаких конкретных тестов, но возможно, что это лучший выбор для методов, которые могут потенциально возвращать большие объемы данных. Однако я не знаю, имеет ли значение для не двоичных данных. Во всяком случае, мысль.
Ответ 2
Мне кажется, что вы используете не правильный DataContractJsonSerializer
. Что странно: вы не определяете атрибут ResponseFormat = ResponseFormat.Json
для метода public SimpleMessage SayHelloObject()
.
Кроме того, если в строке есть {"Message":"Hello World"}
и отображать ее в отладчике, она будет отображаться как "{\"Message\":\"Hello World\"}"
, так что вы видите string json = JsonConvert.Serialize(message);
(Json.Net). Поэтому мне кажется, что в обоих случаях у вас одинаковые результаты.
Чтобы проверить это, используйте клиентское программное обеспечение, которое считывает результаты. См. Примеры
JQuery ajax вызов httpget webmethod (С#) не работает
Могу ли я вернуть JSON из веб-службы .asmx, если ContentType не JSON?
Как создать объект JSON для отправки в AJAX WebService?
ОБНОВЛЕНО. В коде вы определяете метод SayHelloString()
. В результате получается строка. Если вы вызовете метод, эта строка будет еще раз JSON сериализована. Сериализация JSON строки {"Message":"Hello World"}
представляет собой строку с кавычками (см. http://www.json.org/ определение не для объекта, а для строки) или точно строка "{\"Message\":\"Hello World\"}"
. Итак, все правильно с обоими методами вашей веб-службы.
ОБНОВЛЕНО 2. Я рад, что мой совет из части "Обновить" моего ответа помог вам совершить двойную сериализацию JSON.
Тем не менее я бы рекомендовал вам немного изменить решение, чтобы больше оставаться в концепции WCF.
Если вы хотите реализовать пользовательскую кодировку веб-ответа в WCF (см. http://msdn.microsoft.com/en-us/library/ms734675.aspx), ваш метод WCF должен лучше возвращать Message
вместо 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);
}
Вы можете использовать другой формирователь сообщений: например CreateStreamResponse
(или некоторые другие см. http://msdn.microsoft.com/en-us/library/system.servicemodel.web.weboperationcontext_methods(v=VS.100).aspx) вместо CreateTextResponse
.
Если вы хотите установить некоторые дополнительные HTTP-заголовки или код состояния Http (например, в случае некоторой ошибки), вы можете сделать это следующим образом:
OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
ctx.StatusCode = HttpStatusCode.BadRequest;
В конце я хочу повторить свой вопрос из комментария: не могли бы вы объяснить, почему вы хотите использовать Json.Net
вместо DataContractJsonSerializer
? Это улучшение производительности? Вам нужно реализовать сериализацию некоторых типов данных, таких как DateTime
, иначе, как DataContractJsonSerializer
сделать? Или основной причиной вашего выбора Json.Net
является какой-то другой?