Общий десериализация WCF JSON

Я немного новичок в 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 будет null. Такое поведение имеет смысл, но я действительно не знаю, как обойти это. Должен ли я принудительно вводить имена полей на клиенте или есть способ, который я могу согласовать на стороне сервера?

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

Ответы

Ответ 1

Здесь альтернативный способ чтения 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);
            }
        }
    }

Ответ 2

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

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

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

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


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

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

Ответ 3

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

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

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

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

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

Ответ 4

Чтобы достичь моей цели иметь сервис, который может принять полностью произвольный список пар "ключ": "значение" как сырую строку JSON, а затем решить, что делать с ними, не зная "ключ", имена заранее, я совпадал с советом по работе с кассе и аароном.

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

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

Второе примечание: если casperOne упоминается, метод может иметь только один параметр. Этот параметр должен быть 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, но, насколько я могу судить, это самый простой способ добраться туда, куда я хочу пойти.

Ответ 5

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

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