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

时间:2018-06-04 14:21:35

标签: c# odata edmx

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

  

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

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

这意味着这样可以正常工作:

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 =最小”。

     

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

     

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

可以通过将以下行添加到ODataConventionModelBuilder中的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"
                     }
                }
           }
      ]
 }

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

2 个答案:

答案 0 :(得分:3)

我和你在同一条船上。我需要将一些数据公开为纯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()): Working output

答案 1 :(得分:0)

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

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

然后将其注册为复杂类型:

builder.ComplexType<OpenComplexType>();

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

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

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