JSON.NET作为WebAPI 2 OData序列化程序与ODataMediaTypeFormatter

 zhqnnnzhqnn丿n_699 发布于 2023-02-09 14:48

我正在尝试将JSON.NET用作WebAPI 2堆栈中的默认序列化程序.我已经实现了JsonMediaTypeFormatter,其中我使用了JSON.NET序列化程序来序列化/反序列化数据,并创建了JsonContentNegotiator来使用这种媒体类型格式化程序.除OData查询外所有工作正常 - 如果我添加[可查询]元数据ot动作方法,则响应对象不包含任何元数据信息,仅包含实体列表.

小例子.我的行动方法:

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

如果我使用默认的OData序列化并按规则类型调用一些查询(例如 - .../odata/RuleType?$inlinecount=allpages&$skip=0&$top=1),我会收到带有元数据信息和计数属性的经典OData响应:

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

(某些领域跳过,但我有一个空值Notes属性),但如果我加上我JsonContentNegotiatorJsonMediaTypeFormatter一个串行-我只接收实体的名单:

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

(因为这里没有Notes字段NullValueHandling.Ignore)更多.如果我[Queryable]在动作方法中删除属性 - 我收到另一个结果:

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

在这种情况下,我收到了Count,但这里仍然没有元数据.并且odata响应属性名称与默认名称完全不同.

我的思绪在爆炸.我只是想在我的网络应用程序的任何部分使用JSON.NET作为我的序列化程序(因为一些强大的限制).我怎样才能做到这一点?

2 个回答
  • 如果它对其他人有帮助,这就是我在OData中重用Json.NET自定义序列化程序的方法.

    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是通过属性或配置附加了Json.NET序列化程序的自定义类.

    这是转换器类:

    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<YourObject>(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<YourSubObject> subobjects = property.GetValue(serialized) as IList<YourSubObject>;
                                List<ODataComplexValue> subobjects_list = new List<ODataComplexValue>();
                                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<ODataProperty> new_properties = new List<ODataProperty>();
            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<ODataProperty> unsuppressed_properties = new List<ODataProperty>();
            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;
    
        }
    }
    

    最后,这是我的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<ODataProperty> child_properties = new List<ODataProperty>();
            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中重用你的对象,这对我有用.

    2023-02-09 14:51 回答
  • 我已经找到了问题并找到了解决方案.OData使用独立的媒体类型格式化程序,继承自ODataMediaTypeFormatter.此外,OData使用不同的格式化程序进行序列化和反序列化.为了替换这种行为,我们必须实现ODataDeserializerProvider和/或ODataSerializerProvider类的后代,并将这些类添加到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);
        }
    }
    

    我还使用GetEdmTypeReference()和GetEdmType()方法从我的项目中的WebAPI OData源代码中添加了EdmLibsHelper类,因为此类是内部的.

    2023-02-09 14:51 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有