Подтвердить что ты не робот

Как я могу вернуть 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.
}
4b9b3361

Ответ 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 является какой-то другой?