如何在 OData C# 驱动程序中支持嵌套的开放式复杂类型?

How to support a nested open complex type in the OData C# driver?

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

Install-Package Microsoft.AspNet.OData
Install-Package 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 occurred

Message: The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata.metadata=minimal'.

Message: Exception thrown: 'System.InvalidOperationException' in System.Web.OData.dll

Additional information: The given model does not contain the type 'System.Collections.Generic.Dictionary`2[System.String,System.Object]'.


这可以通过将以下行添加到 WebApiConfig.cs 中的 ODataConventionModelBuilder 来解决:

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 也正确序列化嵌套的开放字段? IE。我想要以下结果 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"
                     }
                }
           }
      ]
 }

提前感谢您提供的任何帮助!

Dictionary<string, object> 添加为开放式复杂类型可能会使构建器感到困惑。开放类型需要定义一个包含动态属性的 属性。在Dictionary<string, object>的情况下,似乎认为Keys集合是开放类型的动态属性容器。尝试创建一个类型来定义为复杂类型,如下所示:

public class OpenComplexType
{
    public IDictionary<string, object> DynamicProperties { get; set; }
}

然后注册为复杂类型:

builder.ComplexType<OpenComplexType>();

最后,使用 OpenComplexType 作为类型定义您的 Document 属性:

public class MongoData
{
    public OpenComplexType Document { get; set; }
}

我绝不是 WebAPI OData 库的专家,可能还有其他方法可以通过使用 Dictionary<string, object> 来解决这个问题,但这应该是一个起点。

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

实施 MongoDataSerializer class:

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 class:

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);
    }
}

在您的 Startup.cs 中注册 CustomODataSerializerProvider:

        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()):