Общая десериализация WCF JSON - PullRequest
14 голосов
/ 19 февраля 2010

Я немного новичок в WCF и постараюсь четко описать, что я пытаюсь сделать.

У меня есть веб-сервис WCF, который использует запросы JSON. Я делаю нормально отправку / получение JSON по большей части. Например, следующий код работает хорошо и как ожидалось.

JSON отправлено:

{ "guy": {"FirstName":"Dave"} }

WCF:

    [DataContract]
    public class SomeGuy
    {
        [DataMember]
        public string FirstName { get; set; }
    }

    [OperationContract]
    [WebInvoke(Method = "POST",
               BodyStyle = WebMessageBodyStyle.WrappedRequest,
               RequestFormat = WebMessageFormat.Json,
               ResponseFormat = WebMessageFormat.Json)]
    public string Register(SomeGuy guy)
    {
        return guy.FirstName;
    }

Возвращает объект JSON с "Dave", как и ожидалось. Проблема в том, что я не всегда могу гарантировать, что полученный JSON будет точно соответствовать членам моего DataContract. Например, JSON:

{ "guy": {"firstname":"Dave"} }

не будет правильно сериализован, потому что регистр не совпадает. guy.FirstName будет нулевым. Такое поведение имеет смысл, но я не знаю, как обойти это. Нужно ли принудительно устанавливать имена полей на клиенте или есть способ согласования на стороне сервера?

Возможно связанный вопрос: могу ли я принять и сериализовать универсальный объект JSON в StringDictionary или какую-то простую структуру значения ключа? Поэтому независимо от того, какие имена полей отправляются в JSON, я могу получить доступ к именам и значениям, которые были отправлены мне? Прямо сейчас, единственный способ, которым я могу прочитать данные, которые я получаю, - если они точно соответствуют предопределенному DataContract.

Ответы [ 5 ]

11 голосов
/ 09 сентября 2011

Вот альтернативный способ чтения json в словарь:

[DataContract]
public class Contract
    {
    [DataMember]
    public JsonDictionary Registration { get; set; }
    }

[Serializable]
public class JsonDictionary : ISerializable
    {
    private Dictionary<string, object> m_entries;

    public JsonDictionary()
        {
        m_entries = new Dictionary<string, object>();
        }

    public IEnumerable<KeyValuePair<string, object>> Entries
        {
        get { return m_entries; }
        }

    protected JsonDictionary(SerializationInfo info, StreamingContext context)
        {
        m_entries = new Dictionary<string, object>();
        foreach (var entry in info)
            {
            m_entries.Add(entry.Name, entry.Value);
            }
        }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
        foreach (var entry in m_entries)
            {
            info.AddValue(entry.Key, entry.Value);
            }
        }
    }
4 голосов
/ 19 февраля 2010

Как следует из названия, данные контракт представляют собой набор правил. Если вы хотите надежно сопоставить сообщения с операциями, необходимо соблюдать эти правила.

Почему вы не можете гарантировать, что корпус будет правильным? Если вы просто хотите использовать строчные идентификаторы из JavaScript, вы можете использовать для этого атрибут MessageParameter, но вам все равно придется выбрать специфичное имя.

Теоретически вы можете принять необработанный JSON и десериализовать его вручную (просто возьмите строковый параметр и используйте любую библиотеку JSON для десериализации), но на самом деле это не соответствует духу WCF.

Я думаю, что вам действительно нужно исправить, это не тот факт, что контракт данных учитывает регистр, а тот факт, что JSON неправильно составлен на стороне клиента.


Если вы хотите принять необработанную строку JSON в вашей операции, то измените BodyStyle на WebMessageBodyStyle.Bare и измените сигнатуру вашего метода, чтобы она принимала единственный строковый параметр, который будет заполняться любой строкой JSON, отправленной клиент.

Обратите внимание, что все, что вы получаете - это одна строка , из этого вы должны выполнить весь анализ, сопоставление свойств, проверку и обработку ошибок самостоятельно. Я бы выбрал не тот путь, но если вы готовы взять на себя все усилия и риск, это один из возможных вариантов.

3 голосов
/ 25 февраля 2010

Для достижения моей цели иметь сервис, который может принимать совершенно произвольный список пар «ключ»: «значение» в виде необработанной строки JSON, а затем решать, что с ними делать, не зная «ключ». Имена заранее, я объединил совет Каспера и Аарона.

1-й, чтобы получить доступ к необработанной строке JSON, этот блог MSDN был очень полезен.

Мне не удалось просто изменить параметр одного метода на String, а BodyStyle на WebMessageBodyStyle.Bare без проблем. При установке BodyStyle на Bare убедитесь, что конечная точка behaviorConfiguration установлена ​​на <webHttp/>, а не <enableWebScript/>.

Второе замечание состоит в том, что, как casperOne упомянул , метод может иметь только 1 параметр. Для доступа к необработанному тексту этот параметр должен быть Stream (см. Блог MSDN выше).

Как только у вас есть необработанная строка JSON, нужно просто десериализовать ее в StringDictionary. Я выбрал JSON.Net для этого, и он прекрасно работает. Вот простой пример для иллюстрации.

[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Bare,
    ResponseFormat = WebMessageFormat.Json)]
public string Register(Stream rawJSON)
{ 
    // Convert our raw JSON string into a key, value
    StreamReader sr = new StreamReader(rawJSON);
    Dictionary<string, string> registration =     
        JsonConvert.DeserializeObject<Dictionary<string, string>>(
            sr.ReadToEnd());

    // Loop through the fields we've been sent.
    foreach (KeyValuePair<string, string> pair in registration)
    {
        switch (pair.Key.ToLower())
        {
            case "firstname":
                return pair.Value;
                break;
        }

    }
}

Это позволяет мне принимать произвольный список полей через JSON, а имена полей не чувствительны к регистру. Я знаю, что это не самый строгий подход к целостности данных или к идеальному структурированию сервисов WCF, но, насколько я могу судить, это самый простой способ получить то, чего я хочу.

3 голосов
/ 19 февраля 2010

Вы можете попытаться добавить еще один атрибут DataMember с именем в нижнем регистре, но я предполагаю, что вам нужен регистрозависимый подход к согласованию имен элементов, в этом случае использование дополнительных атрибутов DataMember становится нецелесообразным.

Вы могли бы предоставить реализацию IDataContractSurrogate , но это потребует от вас дополнительной работы, а также много размышлений (нет способа сделать это) это статическим способом, который может быть проверен во время компиляции).

Лично я отказался от использования DataContractSerializer класса , когда дело доходит до JSON. Скорее я использую Json.NET для всех моих потребностей JSON. Я могу видеть, где вы могли бы подключить Json.NET к DataContractSerializer через IDataContractSurrogate, но это все еще будет немного грубо.

Json.NET был простым выбором, учитывая сложность использования DataContractSerializer. Кроме того, добавьте к тому факту, что DataContractSerializer для JSON неправильно обрабатывает значения DateTimeOffset, и для меня это было не сложно, тем более что я работал в ASP.NET MVC окружение (которое позволяет мне формировать результат так, как я хочу).

Это действительно лучший выбор, если вы предоставляете сервисы RESTful, используя JSON в качестве кодировки, вы можете смешивать и сопоставлять это с WCF для предоставления всех конечных точек по всем необходимым протоколам транспорта и сообщений.

0 голосов
/ 19 февраля 2010

Это действительно зависит от того, для чего вы используете данные.Теоретически, вы можете сохранить этот универсальный тип, возвращая строковый тип.Однако данные будут представлять объект, и клиентский элемент управления должен знать, как перечислить возвращаемый объект JSON.Вы можете вернуть тип, который также может помочь.

Тогда в вашем клиентском коде вы можете иметь функцию, которая знает, как определять и читать различные типы.

...