首页 > 解决方案 > 如何在 OData C# 驱动程序中支持嵌套的开放复杂类型?

问题描述

我在 .NET Web Api 项目中使用以下 C# OData 包:

安装包 Microsoft.AspNet.OData
安装包 Microsoft.AspNet.WebApi.OData

在遵循 Microsoft 的示例Use Open Types in OData v4时,只要开放类型不包含其他嵌套的开放复杂类型,一切似乎都按预期工作。

这意味着这将正常工作:

public class WplController : ODataController
{
    private List<AbstractMongoDocument> _documents = new List<AbstractMongoDocument>
    {
        new AbstractMongoDocument
        {
            Id = "2",
            Meta = new MongoMeta(),
            Data = new MongoData
            {
                Document = new Dictionary<string, object>()
                {
                    {"root_open_type", "This works!" },
                }
            }
        }
    };

    [EnableQuery]
    public IQueryable<AbstractMongoDocument> Get()
    {    return _documents.AsQueryable();}
}

虽然这会引发异常

public class WplController : ODataController
{
    private List<AbstractMongoDocument> _documents = new List<AbstractMongoDocument>
    {
        new AbstractMongoDocument
        {
            Id = "1",
            Meta = new MongoMeta(),
            Data = new MongoData
            {
                Document = new Dictionary<string, object>()
                {
                    {"root_open_type", "This works!" },
                    {"nested_open_type",  new Dictionary<string, object>() //Nested dictionary throws exception!
                        {
                            {"field1", "value2" }, 
                            {"field2", "value2" }
                        }
                    }
                }
            }
        }
    };

    [EnableQuery]
    public IQueryable<AbstractMongoDocument> Get()
    {    return _documents.AsQueryable();}
}

例外情况如下:

System.InvalidOperationException 发生

消息:“ObjectContent`1”类型无法序列化内容类型“application/json”的响应正文;odata.metadata=minimal'。

消息:抛出异常:System.Web.OData.dll 中的“System.InvalidOperationException”

附加信息:给定模型不包含类型“System.Collections.Generic.Dictionary`2[System.String,System.Object]”。


这可以通过将以下行添加到ODataConventionModelBuilderin来解决WebApiConfig.cs

builder.ComplexType<Dictionary<string, object>>();

但是,这会导致以下 OData 响应 JSON:

 {
      "@odata.context": "http://localhost:50477/odata/$metadata#wpl",
      "value": 
      [
           {
                "Id": "1",
                "Meta": {},
                "Data": 
                {
                     "root_open_type": "This works!",
                     "nested_open_type": 
                     {
                          "@odata.type": "#System.Collections.Generic.Dictionary_2OfString_Object",
                          "Keys": 
                          [
                               "field1",
                               "field2"
                          ]
                     }
                }
           }
      ]
 }

如何确保 ODate 也正确序列化嵌套的开放字段?即我想要以下生成的 OData JSON:

 {
      "@odata.context": "http://localhost:50477/odata/$metadata#wpl",
      "value": 
      [
           {
                "Id": "1",
                "Meta": {},
                "Data": 
                {
                     "root_open_type": "This works!",
                     "nested_open_type": 
                     {
                          "field1": "value1",
                          "field2": "value2"
                     }
                }
           }
      ]
 }

提前感谢任何潜在的帮助!

标签: c#odataedmx

解决方案


我和你在同一条船上。我需要将一些数据公开为纯 JSON。这是使用ODataUntypedValue该类的有效解决方案。它序列化为您所期望的。我用你的模型和我的模型测试了它。

实现一个MongoDataSerializer类:

public class MongoDataSerializer: ODataResourceSerializer
{
    public MongoDataSerializer(ODataSerializerProvider serializerProvider)
        : base(serializerProvider)
    {
    }

    /// <summary>
    /// Serializes the open complex type as an <see cref="ODataUntypedValue"/>.
    /// </summary>
    /// <param name="graph"></param>
    /// <param name="expectedType"></param>
    /// <param name="writer"></param>
    /// <param name="writeContext"></param>
    public override void WriteObjectInline(
        object graph,
        IEdmTypeReference expectedType,
        ODataWriter writer,
        ODataSerializerContext writeContext)
    {
        // This cast is safe because the type is checked before using this serializer.
        var mongoData = (MongoData)graph;
        var properties = new List<ODataProperty>();

        foreach (var item in mongoData.Document)
        {
            properties.Add(new ODataProperty
            {
                Name = item.Key,
                Value = new ODataUntypedValue
                {
                    RawValue = JsonConvert.SerializeObject(item.Value),
                },
            });
        }

        writer.WriteStart(new ODataResource
        {
            TypeName = expectedType.FullName(),
            Properties = properties,
        });

        writer.WriteEnd();
    }
}

实现一个CustomODataSerializerProvider类:

public class CustomODataSerializerProvider : DefaultODataSerializerProvider
{
    private readonly MongoDataSerializer mongoDataSerializer;

    public CustomODataSerializerProvider(
        IServiceProvider odataServiceProvider)
        : base(odataServiceProvider)
    {
        this.mongoDataSerializer = new MongoDataSerializer(this);
    }

    public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
    {
        if (edmType.FullName() == typeof(MongoData).FullName)
        {
            return this.mongoDataSerializer;
        }

        return base.GetEdmTypeSerializer(edmType);
    }
}

CustomODataSerializerProvider您的Startup.cs

        app.UseMvc(options =>
        {
            var model = builder.GetEdmModel();
            options
                .MapODataServiceRoute(
                    "odata",
                    "odata",
                    b => b
                            .AddService(Microsoft.OData.ServiceLifetime.Scoped, s => model)
                            .AddService<IEnumerable<IODataRoutingConvention>>(
                                Microsoft.OData.ServiceLifetime.Scoped,
                                s => ODataRoutingConventions.CreateDefaultWithAttributeRouting("odata", options))
                            .AddService<ODataSerializerProvider, CustomODataSerializerProvider>(Microsoft.OData.ServiceLifetime.Singleton));
        }

这是使用您的模型的输出(注意属性名称以小写字母开头,因为我启用了ODataConventionModelBuilder.EnableLowerCamelCase()): 工作输出


推荐阅读