JSON.NET как сериализатор OAP для WebAPI 2 и ODataMediaTypeFormatter

Я пытаюсь использовать JSON.NET в качестве сериализатора по умолчанию в стеке WebAPI 2. Я реализовал JsonMediaTypeFormatter, в котором я использовал сериализатор JSON.NET для сериализации / десериализации данных и создал JsonContentNegotiator для использования этого формата. Все работает отлично, за исключением запросов OData – если я добавлю [Queryable] метаданные из метода действия, тогда объект ответа не содержит никакой информации метаданных, только список объектов.

Небольшой пример. Мой метод действия:

[Queryable] public async Task<PageResult> GetRuleType(ODataQueryOptions options) { var ret = await _service.ListRuleTypesAsync(options); return new PageResult( ret, Request.GetNextPageLink(), Request.GetInlineCount()); } 

Если я использую сериализацию по умолчанию OData и вызываю некоторый запрос по типу Rule (например – .../odata/RuleType?$inlinecount=allpages&$skip=0&$top=1 ), я получаю classический ответ OData с информацией метаданных и свойством count :

 odata.metadata ".../odata/$metadata#RuleType" odata.count "2" value 0 { Id: 1 Name: "General" Code: "General" Notes: null } 

(некоторые поля пропущены, но у меня есть свойство Notes с нулевым значением). Но если я добавлю JsonContentNegotiator с JsonMediaTypeFormatter в качестве сериализатора – я получаю только список объектов:

 [ { "Id": 1, "Name": "General", "Code": "General" } ] 

(здесь нет поля примечаний из-за NullValueHandling.Ignore ) Еще больше. Если я удалю атрибут [Queryable] в методе действия – я получаю следующий результат:

 { "Items": [ { "Id": 1, "Name": "General", "Code": "General" } ], "Count": 2 } 

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

Мой ум взорвался. Я просто хочу использовать JSON.NET в качестве своего сериализатора в любой части моего веб-приложения (из-за сильных ограничений). Как я могу это сделать?

Я уже выяснил свою проблему и нашел решение. OData использует отдельные форматы форм мультимедиа, унаследованные от ODataMediaTypeFormatter. Также OData использует различные форматы для сериализации и десериализации. Для замены этого поведения нам необходимо реализовать потомки classов ODataDeserializerProvider и / или ODataSerializerProvider и добавить эти classы в коллекции HttpConfiguration.Formatters

 var odataFormatters = ODataMediaTypeFormatters .Create(new MyODataSerializerProvider(), new MuODataDeserializerProvider()); config.Formatters.AddRange(odataFormatters); 

Пример поставщика небольшой десериализации:

 public class JsonODataDeserializerProvider : ODataDeserializerProvider { public override ODataEdmTypeDeserializer GetEdmTypeDeserializer(IEdmTypeReference edmType) { var kind = GetODataPayloadKind(edmType); return new JsonODataEdmTypeDeserializer(kind, this); } private static ODataPayloadKind GetODataPayloadKind(IEdmTypeReference edmType) { switch (edmType.TypeKind()) { case EdmTypeKind.Entity: return ODataPayloadKind.Entry; case EdmTypeKind.Primitive: case EdmTypeKind.Complex: return ODataPayloadKind.Property; case EdmTypeKind.Collection: IEdmCollectionTypeReference collectionType = edmType.AsCollection(); return collectionType.ElementType().IsEntity() ? ODataPayloadKind.Feed : ODataPayloadKind.Collection; default: return ODataPayloadKind.Entry; } } public override ODataDeserializer GetODataDeserializer(IEdmModel model, Type type, HttpRequestMessage request) { var edmType = model.GetEdmTypeReference(type); return edmType == null ? null : GetEdmTypeDeserializer(edmType); } } 

ODataDeserializer:

 public class JsonODataEdmTypeDeserializer : ODataEdmTypeDeserializer { public JsonODataEdmTypeDeserializer(ODataPayloadKind payloadKind) : base(payloadKind) { } public JsonODataEdmTypeDeserializer(ODataPayloadKind payloadKind, ODataDeserializerProvider deserializerProvider) : base(payloadKind, deserializerProvider) { } public override object Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext) { var data = readContext.Request.Content.ReadAsStringAsync().Result; return JsonConvert.DeserializeObject(data, type); } } 

И я также добавил class EdmLibsHelper из исходного кода WebAPI OData в моем проекте с методами GetEdmTypeReference () и GetEdmType (), потому что этот class является внутренним.

Если это помогает кому-то еще, вот как я повторно использовал свой собственный сериализатор Json.NET в OData.

В Startup введите свой собственный поставщик сериализатора:

  var odataFormatters = ODataMediaTypeFormatters.Create(new MyODataSerializerProvider(), new DefaultODataDeserializerProvider()); config.Formatters.InsertRange(0, odataFormatters); 

Вот мой MyODataSerializerProvider.cs :

  public class MyODataSerializerProvider : DefaultODataSerializerProvider { public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType) { switch (edmType.TypeKind()) { case EdmTypeKind.Enum: ODataEdmTypeSerializer enumSerializer = base.GetEdmTypeSerializer(edmType); return enumSerializer; case EdmTypeKind.Primitive: ODataEdmTypeSerializer primitiveSerializer = base.GetEdmTypeSerializer(edmType); return primitiveSerializer; case EdmTypeKind.Collection: IEdmCollectionTypeReference collectionType = edmType.AsCollection(); if (collectionType.ElementType().IsEntity()) { ODataEdmTypeSerializer feedSerializer = base.GetEdmTypeSerializer(edmType); return feedSerializer; } else { ODataEdmTypeSerializer collectionSerializer = base.GetEdmTypeSerializer(edmType); return collectionSerializer; } case EdmTypeKind.Complex: ODataEdmTypeSerializer complexTypeSerializer = base.GetEdmTypeSerializer(edmType); return complexTypeSerializer; case EdmTypeKind.Entity: ODataEdmTypeSerializer entityTypeSerializer = new MyODataEntityTypeSerializer(this); return entityTypeSerializer; default: return null; } } } 

Затем он вызывает MyODataEntityTypeSerializer.cs :

 public class MyODataEntityTypeSerializer : ODataEntityTypeSerializer { private static Logger logger = LogManager.GetCurrentClassLogger(); public DocsODataEntityTypeSerializer(ODataSerializerProvider serializerProvider) : base(serializerProvider) { } public override ODataEntry CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext) { ODataEntry entry = base.CreateEntry(selectExpandNode, entityInstanceContext); if(entry.TypeName == typeof(YourObject).FullName) { YourObjectEntryConverter converter = new YourObjectEntryConverter(entry); entry = converter.Convert(); } return entry; } } 

Обратите внимание, что YourObject – это ваш собственный class, который имеет подключаемый Json.NET-сериализатор, через атрибут или конфигурацию.

Вот class преобразователя:

 public class YourObjectEntryConverter { private ODataEntry _entry; private string[] _suppressed_properties = { "YourProperty1", "YourProperty2" }; public YourObjectEntryConverter(ODataEntry entry) { _entry = entry; } public ODataEntry Convert() { // 1st pass: create a poco from odata YourObject yours = new YourObject(); PropertyInfo[] properties = typeof(YourObject).GetProperties(); foreach (PropertyInfo property in properties) { foreach (ODataProperty odata_property in _entry.Properties) { if (property.Name == odata_property.Name) { if (odata_property.Value is ODataCollectionValue) { // my json de/serialization populates these; ymmv } else if (odata_property.Value is DateTimeOffset) { DateTimeOffset? dto = odata_property.Value as DateTimeOffset?; property.SetValue(yours, dto.Value.DateTime); } else if (odata_property.Value == null) { property.SetValue(yours, odata_property.Value); } else if (ODataUtils.IsPrimitiveType(odata_property.Value.GetType())) { property.SetValue(yours, odata_property.Value); } // todo complex types break; } } } // 2nd pass: use json serializer in the business layer to add markup // this call fires the "decorators" in YourObjectSerializer.cs via Json.NET string json = JsonConvert.SerializeObject(yours); // suck the newly added info back in YourObject serialized = JsonConvert.DeserializeObject(json); // 3rd pass: scrape the json poco and shovel it back into odata foreach (PropertyInfo property in properties) { foreach (ODataProperty odata_property in _entry.Properties) { if (property.Name == odata_property.Name) { if (odata_property.Value is ODataCollectionValue) { var collection = odata_property.Value as ODataCollectionValue; var collection_typename = property.PropertyType.ToString(); if (collection_typename.Contains("List") && collection_typename.Contains("YourSubObject")) { IList subobjects = property.GetValue(serialized) as IList; List subobjects_list = new List(); foreach(YourSubObject subobject in subobjects) { subobjects_list.Add(ODataUtils.CreateComplexValue(typeof(YourSubObject), subobject)); } collection.Items = subobjects_list.AsEnumerable(); } } else if (odata_property.Value is DateTimeOffset) { DateTimeOffset? dto = odata_property.Value as DateTimeOffset?; property.SetValue(yours, dto.Value.DateTime); } else { object new_value = property.GetValue(serialized); object old_value = property.GetValue(yours); if (null == old_value && null != new_value) { Type t = new_value.GetType(); if (!ODataUtils.IsPrimitiveType(t)) { odata_property.Value = ODataUtils.CreateComplexValue(t, new_value); } else { odata_property.Value = new_value; } } else if (odata_property.Value is Guid) { Guid? new_guid = new_value as Guid?; Guid? old_guid = old_value as Guid?; if (Guid.Empty == old_guid.Value && Guid.Empty != new_guid.Value) { odata_property.Value = new_value; } } } break; } } } // 4th pass: add stuff that json added to the entry List new_properties = new List(); foreach (PropertyInfo property in properties) { object value = property.GetValue(serialized); if (null != value) { bool lost_property = true; // couldn't resist foreach (ODataProperty odata_property in _entry.Properties) { if (property.Name == odata_property.Name) { lost_property = false; break; } } if (lost_property) { ODataProperty new_property = ODataUtils.CreateProperty(property.Name, value); new_properties.Add(new_property); } } } // 5th pass: strip odata properties we don't want to expose externally List unsuppressed_properties = new List(); foreach (ODataProperty odata_property in _entry.Properties) { if (!_suppressed_properties.Contains(odata_property.Name)) { unsuppressed_properties.Add(odata_property); } } unsuppressed_properties.AddRange(new_properties); // from 4th pass _entry.Properties = unsuppressed_properties.AsEnumerable(); return _entry; } } 

Наконец, вот мой class utils:

 public class ODataUtils { public static bool IsPrimitiveType(Type t) { if (!t.IsPrimitive && t != typeof(Decimal) && t != typeof(String) && t != typeof(Guid) && t != typeof(DateTime)) // todo { return false; } return true; } public static ODataProperty CreateProperty(string name, object value) { object property_value = value; if(value != null) { Type t = value.GetType(); if (!IsPrimitiveType(t)) { property_value = CreateComplexValue(t, value); } else if (t == typeof(DateTime) || t == typeof(DateTime?)) { DateTime dt = (DateTime)value; dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc); DateTimeOffset dto = dt; property_value = dto; } } ODataProperty new_property = new ODataProperty() { Name = name, Value = property_value }; return new_property; } public static ODataComplexValue CreateComplexValue(Type type, object value) { ODataComplexValue complex_value = new ODataComplexValue(); complex_value.TypeName = type.ToString(); PropertyInfo[] complex_properties = type.GetProperties(); List child_properties = new List(); foreach (PropertyInfo property in complex_properties) { ODataProperty child_property = CreateProperty(property.Name, property.GetValue(value)); child_properties.Add(child_property); } complex_value.Properties = child_properties.AsEnumerable(); return complex_value; } } 

Все это ужасный взлом, но если у вас есть куча специального кода сериализации Json.NET для ваших объектов, которые вы хотите повторно использовать в OData, это сработало для меня.

  • Сделать пользовательский class Serializable в Objective-c / iPhone?
  • .NET NewtonSoft JSON десериализует карту для другого имени свойства
  • Сериализация объекта в строку
  • Как десериализовать XML-документ
  • записать список объектов в файл
  • Проблема при сохранении данных от Spark-Streaming до Cassandra
  • jQuery serialize не регистрирует флажки
  • Как заставить $ .serialize () учитывать отключенных: элементы ввода?
  • Имеет ли значение то, что я выбираю для serialVersionUID при расширении classов Serializable в Java?
  • Как я могу расширить лексический бросок, чтобы поддерживать перечисленные типы?
  • Что и когда я должен его использовать?
  • Interesting Posts

    Как запустить стеклянную рыбку 4 на порт 80 вместо 8080? root-доступ не является проблемой

    Средства администрирования потеряны

    Что должно быть в моем .gitignore для проекта Android Studio?

    Как сделать моделирование наследования в реляционных базах данных?

    Microsoft Outlook 2007 зависает

    Как я могу поделиться своим проводным подключением к ноутбуку wifi?

    Скопируйте vdi размером более 4 ГБ

    Как запустить GCC скомпилированный вывод на диске NTFS?

    Мне все еще нужно использовать виртуальную память?

    Запуск SWT на основе кросс-платформенной банки на Mac

    Android WebView JellyBean -> Не должно быть: не найдены узлы проверки на основе прямой основы

    Совместное использование или синхронизация домашних папок между компьютерами Mac

    Быстрый способ найти конкретное письмо в почтовом ящике Thunderbird?

    Как заставить пакетный файл ждать / спать в течение нескольких секунд?

    Автоматическая десериализация свойств C #

    Давайте будем гением компьютера.